The code around this assert is:
void planquadrat_t::boden_ersetzen(grund_t *alt, grund_t *neu)
{
assert(alt!=NULL && neu!=NULL && !alt->is_halt() );
...
Now neu and alt should always be valid (see below). is_halt here refers to actual stations rather than waypoints. So the most likely scenario I think is that you were doing something which required changing grounds at a station. A full backtrace from a reproducible error would of course be very handy

boden ersetzen itself is not called from that many places in simutrans:
gebaeude_t* hausbauer_t::baue(karte_t* welt, spieler_t* sp, koord3d pos, int org_layout, const haus_besch_t* besch, void* param)
{
...
for(k.y = 0; k.y < dim.y; k.y ++) {
for(k.x = 0; k.x < dim.x; k.x ++) {
...
grund_t *gr = welt->lookup_kartenboden(pos.get_2d() + k);
...
grund_t *gr2 = new fundament_t(welt, gr->get_pos(), gr->get_grund_hang());
welt->access(gr->get_pos().get_2d())->boden_ersetzen(gr, gr2);
...
Here there are numerous places before boden_ersetzen which assume gr is valid, and gr2 must be valid.
fabrik_t::~fabrik_t()
{
...
if (plan) {
grund_t *gr = plan->get_kartenboden();
...
plan->boden_ersetzen( gr, new boden_t( welt, gr->get_pos(), hang_t::flach ) );
...
Here plan and thus gr must be valid , as is new boden_t.
void fabrik_t::baue(sint32 rotate, bool build_fields, bool force_initial_prodbase)
{
...
for( uint16 i=0; i<fields.get_count(); i++ ) {
const koord k = fields[i].location;
grund_t *gr=welt->lookup_kartenboden(k);
...
grund_t *gr2 = new fundament_t(welt, gr->get_pos(), gr->get_grund_hang());
welt->access(k)->boden_ersetzen(gr, gr2);
...
Here gr should be valid assuming fields have been allocated valid positions.
bool fabrik_t::add_random_field(uint16 probability)
{
...
grund_t *gr = welt->lookup_kartenboden(pos.get_2d()+koord(xoff,yoff));
if (gr != NULL &&
...
build_locations.append(gr);
...
if (!build_locations.empty()) {
grund_t *gr = build_locations.at(simrand(build_locations.get_count()));
...
grund_t *gr2 = new fundament_t(welt, gr->get_pos(), gr->get_grund_hang());
welt->access(k)->boden_ersetzen(gr, gr2);
...
Here gr and gr2 must be valid.
void planquadrat_t::kartenboden_setzen(grund_t *bd)
{
assert(bd);
grund_t *tmp = get_kartenboden();
if(tmp) {
boden_ersetzen(tmp,bd);
Here tmp and bd must be valid.
bool wkz_remover_t::wkz_remover_intern(spieler_t *sp, karte_t *welt, koord3d pos, const char *&msg)
{
...
grund_t *gr = welt->lookup(pos);
if (!gr) {
msg = "";
return false;
}
...
koord k(pos.get_2d());
...
welt->access(k)->boden_ersetzen( gr, new boden_t(welt, gr->get_pos(), welt->recalc_natural_slope(k,dummy) ) );
...
Here gr and boden_t must be valid.
...
const char *wkz_wayremover_t::do_work( karte_t *welt, spieler_t *sp, const koord3d &start, const koord3d &end )
{
...
grund_t *gr=welt->lookup(verbindung.position_bei(i));
// ground can be missing after deleting a bridge ...
if(gr && !gr->ist_wasser()) {
...
grund_t *gr_new = new boden_t(welt, gr->get_pos(), gr->get_grund_hang());
welt->access(gr->get_pos().get_2d())->boden_ersetzen(gr, gr_new);
...
Here gr and gr_new must be valid.