I don't know what you mean by "bound up". These are two separate computational tasks, one of which applies to other modes, so there is no reason for them to be "bound up" and a compelling case for coding them as separate modules. They are connected, but from a programming design perspective all that is required is that one module invokes the other. If they have not been designed and built as separate modules then this is an example of complexity, and increased difficulty of subsequent alteration, that would not occur in a program that was carefully designed before it was built. That is not criticise the work that's been done, especially as it's an open source, volunteer gig, just an observation of how things seem to me, and perhaps a case for documenting, consolidating, and maybe reconfiguring in places, the way the Standard engine works.
The code in question looks like this:
/* this creates passengers and mail for everything is is therefore one of the CPU hogs of the machine
* think trice, before applying optimisation here ...
*/
void stadt_t::step_passagiere()
{
//@author: jamespetts
// Passenger routing and generation metrics.
const uint32 local_passengers_min_distance = welt->get_einstellungen()->get_local_passengers_min_distance();
const uint32 local_passengers_max_distance = welt->get_einstellungen()->get_local_passengers_max_distance();
const uint32 midrange_passengers_min_distance = welt->get_einstellungen()->get_midrange_passengers_min_distance();
const uint32 midrange_passengers_max_distance = welt->get_einstellungen()->get_midrange_passengers_max_distance();
const uint32 longdistance_passengers_min_distance = welt->get_einstellungen()->get_longdistance_passengers_min_distance();
const uint32 longdistance_passengers_max_distance = welt->get_einstellungen()->get_longdistance_passengers_max_distance();
const uint16 min_local_tolerance = welt->get_einstellungen()->get_min_local_tolerance();
const uint16 max_local_tolerance = max(0, welt->get_einstellungen()->get_max_local_tolerance() - min_local_tolerance);
const uint16 min_midrange_tolerance = welt->get_einstellungen()->get_min_midrange_tolerance();
const uint16 max_midrange_tolerance = max( 0, welt->get_einstellungen()->get_max_midrange_tolerance() - min_midrange_tolerance);
const uint16 min_longdistance_tolerance = welt->get_einstellungen()->get_min_longdistance_tolerance();
const uint16 max_longdistance_tolerance = max(0, welt->get_einstellungen()->get_max_longdistance_tolerance() - min_longdistance_tolerance);
const uint8 passenger_packet_size = welt->get_einstellungen()->get_passenger_routing_packet_size();
const uint8 passenger_routing_local_chance = welt->get_einstellungen()->get_passenger_routing_local_chance();
const uint8 passenger_routing_midrange_chance = welt->get_einstellungen()->get_passenger_routing_midrange_chance();
// DBG_MESSAGE("stadt_t::step_passagiere()", "%s step_passagiere called (%d,%d - %d,%d)\n", name, li, ob, re, un);
// long t0 = get_current_time_millis();
// post oder pax erzeugen ?
// "post or generate pax"
const ware_besch_t* wtyp;
if (simrand(400) < 300)
{
wtyp = warenbauer_t::passagiere;
}
else
{
wtyp = warenbauer_t::post;
}
const int history_type = (wtyp == warenbauer_t::passagiere) ? HIST_PAS_TRANSPORTED : HIST_MAIL_TRANSPORTED;
// restart at first buiulding?
if (step_count >= buildings.get_count())
{
step_count = 0;
}
if(buildings.get_count()==0)
{
return;
}
const gebaeude_t* gb = buildings[step_count];
// prissi: since now backtravels occur, we damp the numbers a little
const int num_pax =
(wtyp == warenbauer_t::passagiere) ?
(gb->get_tile()->get_besch()->get_level() + 6) >> 2 :
max(1,(gb->get_tile()->get_besch()->get_post_level() + 5) >> 4);
// Hajo: track number of generated passengers.
city_history_year[0][history_type+1] += num_pax;
city_history_month[0][history_type+1] += num_pax;
// create pedestrians in the near area?
if (welt->get_einstellungen()->get_random_pedestrians() && wtyp == warenbauer_t::passagiere)
{
haltestelle_t::erzeuge_fussgaenger(welt, gb->get_pos(), num_pax);
}
// suitable start search
const koord k = gb->get_pos().get_2d();
const planquadrat_t* plan = welt->lookup(k);
const halthandle_t* halt_list = plan->get_haltlist();
minivec_tpl<halthandle_t> start_halts(plan->get_haltlist_count());
for (int h = plan->get_haltlist_count() - 1; h >= 0; h--)
{
halthandle_t halt = halt_list[h];
if (halt->is_enabled(wtyp) && !halt->is_overcrowded(wtyp->get_catg_index()))
{
start_halts.append(halt);
}
}
INT_CHECK( "simcity 2282" );
// Check whether this batch of passengers has access to a private car each.
// Check run in batches to save computational effort.
const sint16 private_car_percent = wtyp == warenbauer_t::passagiere ? get_private_car_ownership(welt->get_timeline_year_month()) : 0;
// Only passengers have private cars
const bool has_private_car = private_car_percent > 0 ? simrand(100) <= (uint16)private_car_percent : false;
// Record the most useful set of information about why passengers cannot reach their chosen destination:
// Too slow > overcrowded > no route. Tiebreaker: higher destination preference.
koord best_bad_destination;
uint8 best_bad_start_halt;
bool too_slow_already_set;
//Only continue if there are suitable start halts nearby, or the passengers have their own car.
if(start_halts.get_count() > 0 || has_private_car)
{
//Add 1 because the simuconf.tab setting is for maximum *alternative* destinations, whereas we need maximum *actual* desintations
// const uint8 max_destinations = has_private_car ? 1 : (welt->get_einstellungen()->get_max_alternative_destinations() < 16 ? welt->get_einstellungen()->get_max_alternative_destinations() : 15) + 1;
// Passengers with a private car will not tolerate second best destinations,
// and will use their private car to get to their first choice destination
// regardless of whether they might go to other destinations by public transport.
// Now that private cars also have a journey time tolerance and check whether the roads are connected, passengers *might*
// not be able to get to their destination by private car either, so the above is redundant.
const uint8 max_destinations = (welt->get_einstellungen()->get_max_alternative_destinations() < 16 ? welt->get_einstellungen()->get_max_alternative_destinations() : 15) + 1;
// Find passenger destination
for (int pax_routed = 0; pax_routed < num_pax; pax_routed += passenger_packet_size)
{
/* number of passengers that want to travel
* Hajo: for efficiency we try to route not every
* single pax, but packets. If possible, we do 7 passengers at a time
* the last packet might have less then 7 pax
* Number now not fixed at 7, but set in simuconf.tab (@author: jamespetts)*/
int pax_left_to_do = min(passenger_packet_size, num_pax - pax_routed);
// search target for the passenger
pax_zieltyp will_return;
const uint8 destination_count = simrand(max_destinations) + 1;
// Split passengers: between local, midrange and long-distance
// according to the percentages set in simuconf.tab.
// Note: a random town will be found if there are no towns within range.
const uint8 passenger_routing_choice = simrand(100);
const journey_distance_type range =
passenger_routing_choice <= passenger_routing_local_chance ?
local :
passenger_routing_choice <= (passenger_routing_local_chance + passenger_routing_midrange_chance) ?
midrange : longdistance;
const uint16 tolerance =
wtyp != warenbauer_t::passagiere ?
0 :
range == local ?
simrand(max_local_tolerance) + min_local_tolerance :
range == midrange ?
simrand(max_midrange_tolerance) + min_midrange_tolerance :
simrand(max_longdistance_tolerance) + min_longdistance_tolerance;
destination destinations[16];
for(int destinations_assigned = 0; destinations_assigned <= destination_count; destinations_assigned ++)
{
if(range == local)
{
//Local - a designated proportion will automatically go to destinations within the town.
if((float)passenger_routing_choice <= adjusted_passenger_routing_local_chance)
{
// Will always be a destination in the current town.
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, 0, local_passengers_max_distance, k);
}
else
{
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, local_passengers_min_distance, local_passengers_max_distance, k);
}
}
else if(range == midrange)
{
//Medium
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, midrange_passengers_min_distance, midrange_passengers_max_distance, k);
}
else
//else if(range == longdistance)
{
//Long distance
destinations[destinations_assigned] = finde_passagier_ziel(&will_return, longdistance_passengers_min_distance, longdistance_passengers_max_distance, k); //"Ziel" = "target" (Google)
}
}
INT_CHECK( "simcity 2371" );
uint8 current_destination = 0;
route_status route_good = no_route;
const uint16 max_walking_distance = welt->get_einstellungen()->get_max_walking_distance();
uint16 car_minutes = 65535;
best_bad_destination = destinations[0].location;
best_bad_start_halt = 0;
too_slow_already_set = false;
while(route_good != good && route_good != private_car_only && current_destination < destination_count)
{
// Dario: Check if there's a stop near destination
const planquadrat_t* dest_plan = welt->lookup(destinations[current_destination].location);
const halthandle_t* dest_list = dest_plan->get_haltlist();
// Knightly : we can avoid duplicated efforts by building destination halt list here at the same time
minivec_tpl<halthandle_t> destination_list(dest_plan->get_haltlist_count());
halthandle_t start_halt;
// Check whether the destination is within walking distance first.
// @author: jamespetts, December 2009
if(accurate_distance(destinations[current_destination].location, k) <= max_walking_distance)
{
// Passengers will always walk if they are close enough.
route_good = can_walk;
}
if(route_good != can_walk)
{
for (int h = dest_plan->get_haltlist_count() - 1; h >= 0; h--)
{
halthandle_t halt = dest_list[h];
if (halt->is_enabled(wtyp))
{
destination_list.append(halt);
ITERATE(start_halts, i)
{
if(halt == start_halts[i])
{
route_good = can_walk;
start_halt = start_halts[i];
// Mail does not walk, but people delivering it do.
break;
}
}
}
}
}
// check, if they can walk there?
if (route_good == can_walk)
{
break;
}
// ok, they are not in walking distance
// Check whether public transport can be used.
ware_t pax(wtyp); //Journey start information needs to be added later.
pax.set_zielpos(destinations[current_destination].location);
pax.menge = (wtyp == warenbauer_t::passagiere ? pax_left_to_do : 1 );
//"Menge" = volume (Google)
// now, finally search a route; this consumes most of the time
uint16 best_journey_time = 65535;
uint8 best_start_halt = 0;
ITERATE(start_halts,i)
{
halthandle_t current_halt = start_halts[i];
uint16 current_journey_time = current_halt->find_route(&destination_list, pax, best_journey_time);
if(current_journey_time < best_journey_time)
{
best_journey_time = current_journey_time;
best_start_halt = i;
}
if(pax.get_ziel().is_bound())
{
route_good = good;
}
}
// Check first whether the best route is outside
// the passengers' tolerance.
if(route_good == good && tolerance > 0 && best_journey_time > tolerance)
{
route_good = too_slow;
if(!too_slow_already_set)
{
best_bad_destination = destinations[current_destination].location;
best_bad_start_halt = best_start_halt;
too_slow_already_set = true;
}
}
else
{
// All passengers will use the quickest route.
if(start_halts.get_count() > 0)
{
start_halt = start_halts[best_start_halt];
}
}
INT_CHECK("simcity.cc 2882");
if(has_private_car)
{
const uint32 straight_line_distance = accurate_distance(k, destinations[current_destination].location);
uint16 time_per_tile = 65535;
switch(destinations[current_destination].type)
{
case 1:
//Town
time_per_tile = check_road_connexion_to(destinations[current_destination].object.town);
break;
case FACTORY_PAX:
time_per_tile = check_road_connexion_to(destinations[current_destination].object.industry);
break;
case TOURIST_PAX:
time_per_tile = check_road_connexion_to(destinations[current_destination].object.attraction);
break;
default:
//Some error - this should not be reached.
dbg->error("simcity.cc", "Incorrect destination type detected");
};
if(time_per_tile < 65535)
{
// *Tenths* of minutes used here.
car_minutes = time_per_tile * straight_line_distance;
}
else
{
car_minutes = 65535;
}
}
if(car_minutes <= tolerance)
{
if(route_good != good)
{
// The passengers can get to their destination by car but not by public transport.
// Therefore, they will certainly use their car.
route_good = private_car_only;
}
else
{
// The passengers can get to their destination both by car and public transport.
// Choose which they prefer.
// Now, decide whether passengers would prefer to use their private cars,
// even though they can travel by public transport.
const uint32 distance = accurate_distance(destinations[current_destination].location, k);
//Weighted random.
uint8 private_car_chance = simrand(100);
if(private_car_chance >= 1)
{
// The basic preference for using a private car if available.
sint16 car_preference = welt->get_einstellungen()->get_base_car_preference_percent();
//First, adjust for distance. For very long-distance journies, cars are less popular.
if(distance > (midrange_passengers_max_distance * 3))
{
if(distance >= longdistance_passengers_max_distance)
{
car_preference /= 10;
}
else
{
const float proportion = ((float)distance - (float)(midrange_passengers_max_distance * 3.0F)) / (float)longdistance_passengers_max_distance;
car_preference /= (10 * proportion);
}
}
// Secondly, congestion. Drivers will turn to public transport if the origin or destination towns are congested.
// This percentage of drivers will prefer to use the car however congested that it is.
const sint16 always_prefer_car_percent = welt->get_einstellungen()->get_always_prefer_car_percent();
//Average congestion of origin and destination towns, and, at the same time, reduce factor.
uint8 congestion_total;
if(destinations[current_destination].type == 1 && destinations[current_destination].object.town != NULL)
{
congestion_total = (city_history_month[0][HIST_CONGESTION] + destinations[current_destination].object.town->get_congestion()) / 4;
}
else
{
congestion_total = (city_history_month[0][HIST_CONGESTION] / 1.33);
}
car_preference = ((car_preference - congestion_total) > always_prefer_car_percent) ? car_preference - congestion_total : always_prefer_car_percent;
// Thirdly adjust for service quality of the public transport.
// Compare best journey speed on public transport with the
// likely private car journey time.
INT_CHECK( "simcity 3004" );
const float proportion = ((float)best_journey_time / (float)car_minutes) * car_minutes > best_journey_time ? 1.25F : 0.75F;
car_preference *= proportion;
// If identical, no adjustment.
//Fourthly, the number of unhappy passengers at the start station compared with the number of happy passengers.
float unhappy_factor = start_halt->get_unhappy_proportion(0);
if(unhappy_factor > 0.8F)
{
private_car_chance /= unhappy_factor;
}
//Finally, determine whether the private car is used.
if(private_car_chance <= car_preference)
{
// Private cars chosen by preference even though public transport is possible.
route_good = private_car_only;
}
}
}
}
if(route_good == can_walk)
{
// so we have happy passengers
// Passengers who can walk to their destination may be happy about it,
// but they are not happy *because* the player has made them happy.
// Therefore, they should not show on the station's happy graph.
// @author: jamespetts, December 2009
//start_halt->add_pax_happy(pax_left_to_do);
merke_passagier_ziel(destinations[0].location, COL_DARK_YELLOW);
if (welt->get_einstellungen()->get_random_pedestrians() && wtyp == warenbauer_t::passagiere)
{
haltestelle_t::erzeuge_fussgaenger(welt, start_halt->get_basis_pos3d(), pax_left_to_do);
}
// They should show that they have been transported, however, since
// these figures are used for city growth calculations.
city_history_year[0][history_type] += pax_left_to_do;
city_history_month[0][history_type] += pax_left_to_do;
}
else if(route_good == good)
{
// Passengers can and will use public transport.
pax.arrival_time = welt->get_zeit_ms();
pax.set_origin(start_halt);
start_halt->starte_mit_route(pax);
merke_passagier_ziel(destinations[current_destination].location, COL_YELLOW);
}
else if(route_good == private_car_only)
{
// Passengers have either chosen to use their car or have no other choice.
stadt_t* const destination_town = destinations[current_destination].type == 1 ? destinations[current_destination].object.town : NULL;
set_private_car_trip(num_pax, destination_town);
merke_passagier_ziel(destinations[current_destination].location, COL_TURQUOISE);
#ifdef DESTINATION_CITYCARS
erzeuge_verkehrsteilnehmer(k, step_count, destinations[current_destination].location);
}
#endif
// send them also back
// (Calculate a return journey)
if(will_return != no_return && route_good == good || route_good == private_car_only)
{
// this comes most of the times for free and balances also the amounts!
halthandle_t ret_halt = pax.get_ziel();
bool return_in_private_car = private_car_only || !ret_halt.is_bound();
if(!return_in_private_car)
{
// we just have to ensure, the ware can be delivered at this station
bool found = false;
for (uint i = 0; i < plan->get_haltlist_count(); i++)
{
halthandle_t test_halt = halt_list[i];
if(test_halt->is_enabled(wtyp) && (start_halt == test_halt || test_halt->get_connexions(wtyp->get_catg_index())->access(start_halt) != NULL))
{
found = true;
start_halt = test_halt;
break;
}
}
// now try to add them to the target halt
if(!ret_halt->is_overcrowded(wtyp->get_catg_index()))
{
// prissi: not overcrowded and can recieve => add them
if(found)
{
ware_t return_pax(wtyp, ret_halt);
if( will_return != town_return && wtyp==warenbauer_t::post )
{
// attractions/factory generate more mail than they recieve
return_pax.menge = pax_left_to_do * 3;
}
else
{
// use normal amount for return pas/mail
return_pax.menge = pax_left_to_do;
}
return_pax.set_zielpos(k);
return_pax.set_ziel(start_halt);
if(ret_halt->find_route(return_pax) != 65535)
{
return_pax.arrival_time = welt->get_zeit_ms();
ret_halt->starte_mit_route(return_pax);
merke_passagier_ziel(destinations[current_destination].location, COL_YELLOW);
//ret_halt->add_pax_happy(pax_left_to_do);
// Experimental 7.2 and onwards does this when passengers reach their destination
}
}
else
{
// no route back
if(car_minutes < 65535)
{
// This assumes that the journey time in both directions is identical: but
// this may not be so if there are one-way routes.
return_in_private_car = true;
}
else
{
ret_halt->add_pax_no_route(pax_left_to_do);
merke_passagier_ziel(destinations[current_destination].location, COL_DARK_ORANGE);
}
}
}
else
{
// Return halt crowded. Either return by car or mark unhappy.
if(car_minutes < 65535)
{
return_in_private_car = true;
}
else
{
ret_halt->add_pax_unhappy(pax_left_to_do);
merke_passagier_ziel(destinations[current_destination].location, COL_RED);
}
}
}
if(return_in_private_car)
{
if(car_minutes < 65535)
{
// Do not check tolerance, as they must come back!
stadt_t* const destination_town = destinations[0].type == 1 ? destinations[0].object.town : NULL;
set_private_car_trip(num_pax, destination_town);
merke_passagier_ziel(destinations[current_destination].location, COL_TURQUOISE);
#ifdef DESTINATION_CITYCARS
//citycars with destination
erzeuge_verkehrsteilnehmer(k, step_count, destinations[0].location);
#endif
}
else
{
if(ret_halt.is_bound())
{
ret_halt->add_pax_no_route(pax_left_to_do);
}
merke_passagier_ziel(destinations[current_destination].location, COL_DARK_ORANGE);
}
}
} // Returning passengers
INT_CHECK( "simcity 3128" );
current_destination ++;
} // While loop (route_good)
if((route_good != good && route_good != private_car_only) && start_halts.get_count() > 0)
{
// Passengers/mail cannot reach their destination at all, either by public transport or private car,
// *but* there are some stops in their locality. Record their inability to get where they are going
// at those local stops accordingly.
halthandle_t start_halt = start_halts[best_bad_start_halt]; //If there is no route, it does not matter where passengers express their unhappiness.
if(!has_private_car || car_minutes > tolerance)
{
// If the passengers are able to use a private car, then they should not record as "no route"
if(too_slow_already_set)
{
if(start_halt.is_bound())
{
start_halt->add_pax_too_slow(pax_left_to_do);
}
merke_passagier_ziel(best_bad_destination, COL_LIGHT_PURPLE);
}
else
{
if(start_halt.is_bound())
{
start_halt->add_pax_no_route(pax_left_to_do);
}
merke_passagier_ziel(destinations[0].location, COL_DARK_ORANGE);
}
}
}
} // For loop (passenger/mail packets)
} // If statement (are some start halts, or passengers have private car)
else
{
// The unhappy passengers will be added to all crowded stops
// however, there might be no stop too
// NOTE: Because of the conditional statement in the original loop,
// reaching this code means that passengers must not be able to use
// a private car for this journey.
// all passengers without suitable start:
// fake one ride to get a proper display of destinations (although there may be more) ...
pax_zieltyp will_return;
//const koord ziel = finde_passagier_ziel(&will_return);
destination destination_now = finde_passagier_ziel(&will_return);
// First, check whether the passengers can *walk*. Just because
// they do not have a start halt does not mean that they cannot
// walk to their destination!
const double tile_distance = accurate_distance(k, destination_now.location);
if(tile_distance < welt->get_einstellungen()->get_max_walking_distance())
{
// Passengers will walk to their destination if it is within the specified range.
// (Default: 1.5km)
merke_passagier_ziel(destination_now.location, COL_DARK_YELLOW);
city_history_year[0][history_type] += num_pax;
city_history_month[0][history_type] += num_pax;
}
else
{
// If the passengers cannot walk, they will be unhappy.
bool crowded_halts = false;
for( uint h=0; h<plan->get_haltlist_count(); h++ )
{
halthandle_t halt = halt_list[h];
if (halt->is_enabled(wtyp))
{
halt->add_pax_unhappy(num_pax);
crowded_halts = true;
}
}
// Only show as being overcrowded if there are, in fact, potentially suitable but overcrowded stops.
if(crowded_halts)
{
merke_passagier_ziel(destination_now.location, COL_RED);
}
else
{
merke_passagier_ziel(destination_now.location, COL_DARK_ORANGE);
}
// we do not show no route for destination stop!
}
}
// long t1 = get_current_time_millis();
// DBG_MESSAGE("stadt_t::step_passagiere()", "Zeit f�r Passagierstep: %ld ms\n", (long)(t1 - t0));
}
A very rough overview of how it works is as follows:
* Cities generate passengers from particular buildings. The numbers depend on the density of the buildings and the passenger factor set in simuconf.tab. The numbers generated are logged appropriately.
* The passengers are either given or not given access to a private car, using a weighted random system, the weightings being based on the percentage of passengers with access to a private car at that point in time (set by using an external file, privatecar.tab).
* Passengers check whether there are any places where they can embark on transport ('bus stops, railway stations, etc.) within their radius.
* The passengers are assigned a destination building using a weighted random system based on the type of building (city building, industry, tourist attraction) and geographical distance from the origin (short-range, mid-range, long-distance*).
* Passengers are assigned a journey time tolerance level.
* Passengers check whether they can get to their destination, either by walking (if it is very close), taking the player's transport or taking their private car.
* If passengers can walk, they do so.
* If the passengers can take their private car (if they have one) but not any other means, they do so if the journey time does not exceed their tolerance.
* If passengers can use player transport but no other means, they calculate the quickest journey on player transport, and use that if the journey time does not exceed their tolerance.
* If passengers can use either private car or player transport, they evaluate which to use based on factors such as the relative speed of the journeys, the congestion in the origin and destination cities (the most important factor) and a fixed car preference value set in simuconf.tab. There is a certain percentage of passengers who will always take their private cars if they possibly can - this percentage is also set in simuconf.tab. They then take the appropriate mode, if the journey time does not exceed their tolerance.
* Once the passengers know which route that they are taking, their journey is logged in the appropriate ways. If a private car is used, an appropriate car graphic is generated, and traffic (and therefore congestion) numbers are adjusted accordingly in the origin and (if applicable) destination cities.
* If the passengers cannot reach their destination by any means, this too is logged (differentially depending on whether there is no route, or there is a route that exceeds their tolerance).
The above process also works for mail, except that mail cannot either walk or take a private car. There is also a mechanism to generate returning passengers, which, for the sake of parsimony, I have omitted from the rough description above. This is what I mean by "bound up", and it does not seem obvious that one could or should have a separate module for the private car journeys here.
* I am considering removing this element, as it might be that the journey time tolerances feature can be made, more realistically, to have the desired effect of this in any event.
The answers to these questions should have been determined before any of it was coded. Perhaps they were, and maybe the decisions were documented somewhere.
Ahh, I never really saw the need to have a conceptual separation of features into discrete units (as, after all, reality is not so compartmentalised). The approach was always simply to simulate, in so far as relevant and practicable, the various dynamics of reality and their interaction with one another.
The reason this narrative has been dragging on for so long is because we don't have any such document. What I envisage is more a case of a few points against each feature/dynamic describing what assumptions are made in the sim, and what the sim is supposed to represent. If we don't have that, and the code is our only reference, then we can't evaluate the code against what it's supposed to achieve, or properly consider alternative ways of coding.
So that I have an idea of where we're heading with this, can you give me an example/extract, perhaps based on the features described above relating to the passenger generation code?
The point is not about playing styles, it's about the long-term consequences of not thinking far enough ahead.
Ahh, that was more a casual aside than anything related to the main discussion!
Experimental is not parasitic, it is an extension of Standard. Experimental doesn't pick bits out of Standard, it builds additional features upon Standard's foundations. To take a parasitic approach one doesn't need to understand how Standard works, just identify the bits one wants to steal. To build an extension one has to first understand the foundation upon which one is building. So I see a process like this:
1. Document and understand what Standard does, and also what it doesn't do
2. Identify everything that Standard does that is undesirable, and what it doesn't do that is desirable
3. Develop designs that integrate the desired changes into the simplest possible game, coded in the simplest possible way
4. Assess each design against criteria such as coding hours required, modularity, expandability, simplicity of calibration, etc, and select one
5. Start building, and so on
This looks like the approach that one might adopt if one were starting Experimental to-morrow. Presumably, what we need to do now is to document and understand what Experimental does and doesn't do and evaluate then what of that is undesirable or missing?
Can you articulate that vision? Because I can't see what it is, and am concerned that it may contain fatal contradictions that will only otherwise become apparent after investing hundreds of hours into development trying to make work what cannot work (an open-ended process if one never realises that it can't work). I think a couple of steps have been skipped, including developing those abstract ideas into practical ones before rushing into coding Experimental. No, it is not easy to visualise everything at once, although it does become easier with practice. If all of those things are not documented in one place then (1) you will have great difficulty entertaining all of them at once and (2) no one else will be able to contribute.
The vision that I have for Experimental might be considered rather abstract, but it is this: given the basic elements and design assumptions of Simutrans (the simulation of operating a transport company by owning and running vehicles and ways, etc.), develop a version of it that will mean that the decisions that are made in the game by players have as similar effects as possible/practical on the things simulated by the game as in reality, such that a player playing such as to take game-optimal decisions will make the same decisions (in so far as possible) as, within the constraints of what is actually modelled by Simutrans, would have been similarly optimal decisions in reality, with as similar a set of consequences (within the constraints of what is actually modelled by Simutrans) as possible, such that, given realistic starting parameters (the giving of which is also a design goal of Experimental in so far as that is not possible in Standard), the development of transport networks in Simutrans by players making game-optimal decisions will follow as closely as possible the actual historical development of transport networks as they were actually developed, or might have been developed were it not for outside factors (wars, nationalisations, etc.) that are not inherent to the economics of transportation but were historically accidental, whilst still maintaining a game that is engaging and fun to play (at least for those who can tolerate a relatively high degree of complexity), and further (and relatedly) to mean that players making decisions in the game can make them on the basis of an abstract or heuristic understanding of real-world transportation, without having to engage in Simutrans-specific number crunching to calculate the optimal choice. At the same time, certain ancillary or secondary features that are not directed at that primary goal, but improve the user experience of the game (such as the vehicle replacer, or perhaps more cosmetic features such as the mooted livery variation system) are also desirable to be added along the way so long as they do not interfere with the primary goal. I like the idea that players (and, indeed, developers!) can
learn about why things are as they are in transport terms by playing the game and seeing the various economic elements interact (which is one reason that I am particularly keen on using historical figures where possible).
How can one possibly build a simulation model without first deciding what dynamics need to be modelled? Even if the model was sound one wouldn't know what it represented or how to interact with it. I can help, but only in a culture of developing understanding before trying to build the finished product.I think we need to follow the steps I listed above.
Ahh, I think that I was not as clear as I might have been - I thought that I did have a good understanding of what needed to be modelled until you pointed out the various missing things that rendered good balancing impossible; I had also periodically realised that important dynamics of which I had not hitherto thought had not been modelled. The problem was more that I don't know what the game is missing in terms of dynamics that I don't know or don't remember exist: it is the epistemological problem of not knowing the full extent of one's ignorance.
As for evaluating what dynamics to include, it usually only takes a few minutes to evaluate the kind of difference a feature could make, and it's a good filter - if someone keeps insisting that an insignificant dynamic must be included, it shouldn't be difficult to produce a few calculations to shut them up and help them set their mind on something more significant. The evaluation process is what tells us which trees are big, and which ones are small. That is a picture that develops in one's mind over time, but the nature of a collaborative distributed effort means it also needs to be documented so we don't have to keep repeating our conclusions. To revisit some Simutrans history, this post lists things that "will never happen" because they were deemed insignificant, too difficult etc, but it doesn't present the assumptions/calculations that were used to reach the conclusions. Others have since revisited them and now some of these things are happening. We need to "prove" that something does or doesn't add anything to the game, or quantify the difference it will make so we can reach a sensible and defensible decision on whether or not to include it. Sounds like work but it takes out all the stress and confusion, and stops all the "why didn't you include x?" traffic, so in my experience it ends up feeling like less work.
There may be much to be said for this approach, in that case, although I am still a tad unclear on what the list/specification might look like and how detailed that it should be: the example requested above would be much appreciated in that respect.
I have a degree in applied mathematics and used to analyse and model supply chains and expansion options for a living, especially mine-rail-port systems. Lost my health a few years back and had to quit work. Health is slowly improving - not long ago I couldn't have kept up with this discussion.
I hope, in that case, that our discussions are contributing to, and not detracting from, your health!
In the previous version, there were not enough passengers generated.
In the current version, there are too many passengers generated.
This is a pakset thing, not an Experimental thing, but the point applies, of course, to the pakset development.