1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2025-05-09 10:44:07 +00:00

rogueviz::ads:: using more detailed ds/ads ships, sharing scale with main RogueViz

This commit is contained in:
Zeno Rogue 2025-02-09 01:10:31 +01:00
parent 8ba1b97ebe
commit 7b33cf435c
9 changed files with 154 additions and 74 deletions

View File

@ -95,6 +95,13 @@ void run_ads_game_hooks() {
rogueviz::rv_hook(hooks_nextland, 0, ads_nextland);
}
void srun_size_hooks() {
rogueviz::rv_hook(hooks_scalefactor, 100, [] (geometry_information *i) {
i->scalefactor = vid.creature_scale / 3;
});
rogueviz::rv_hook(hooks_cgi_string, 100, [] (string& s) { s += " ads"; });
}
void run_ads_game() {
if(!sl2) set_geometry(gTwistedProduct);
@ -103,6 +110,7 @@ void run_ads_game() {
hybrid::csteps = 0;
hybrid::reconfigure();
}
run_size_hooks();
run_ads_game_hooks();
start_game();
@ -175,6 +183,12 @@ void default_settings() {
lps_add(lps_relhell_ads_spacetime, vid.grid, false);
lps_add(lps_relhell_ads_spacetime, slr::range_xy, 2.);
lps_add(lps_relhell_ads_spacetime, slr::range_z, 2.);
charstyle& cs = getcs();
lps_add(lps_relhell, cs.skincolor, 0xFFFFFFFF);
lps_add(lps_relhell, cs.eyecolor, 0x8080FFFF);
lps_add(lps_relhell, cs.dresscolor, 0xFFC0C0FF);
lps_add(lps_relhell, cs.haircolor, 0xC0FFC0FF);
}
void gamedata(hr::gamedata* gd) {
@ -213,7 +227,7 @@ void run_ads_game_std() {
}
void change_scale(ld s) {
ads_scale *= s;
vid.creature_scale *= s;
rock_density /= (s * s);
rock_max_rapidity *= s;
ads_simspeed *= s;
@ -244,10 +258,10 @@ auto shot_hooks =
-> editable(0, 2*TAU, TAU/4, "AdS game speed", "Controls the speed of the game, in absolute units per second.", 's');
param_f(ds_simspeed, "ds_game_simspeed")
-> editable(0, 2*TAU, TAU/4, "dS game speed", "Controls the speed of the game, in absolute units per second.", 's');
param_f(ads_scale, "ads_game_scale")
/*param_f(ads_scale, "ads_game_scale")
-> editable(0, 2, 0.1, "AdS game scale", "Controls the scaling of game objects.", 'c');
param_f(ds_scale, "ds_game_scale")
-> editable(0, 2, 0.1, "dS game scale", "Controls the scaling of game objects.", 'c');
-> editable(0, 2, 0.1, "dS game scale", "Controls the scaling of game objects.", 'c'); */
param_f(ads_accel, "ads_game_accel")
-> editable(0, 30, 1, "AdS acceleration", "Controls your ship's acceleration, in absolute units per second squared.", 'a');
param_f(ds_accel, "ds_game_accel")

View File

@ -183,7 +183,7 @@ bool ads_turn(int idelta) {
if(!paused) {
pdata.fuel -= dv;
gen_particles(rpoisson(dv*fuel_particle_qty), vctr, ads_inverse(current * vctrV) * spin(ang*degree+M_PI) * twist::uxpush(0.06 * ads_scale), rsrc_color[rtFuel], fuel_particle_rapidity, fuel_particle_life, 0.02);
gen_particles(rpoisson(dv*fuel_particle_qty), vctr, ads_inverse(current * vctrV) * spin(ang*degree+M_PI) * twist::uxpush(0.06 * get_scale()), rsrc_color[rtFuel], fuel_particle_rapidity, fuel_particle_life, 0.02);
}
ld tc = 0;

View File

@ -99,6 +99,8 @@ void draw_game_cell(const cell_to_draw& cd) {
queuecurve(shiftless(Id), out, col, PPR::WALL);
}
ld ads_scale = get_scale();
if(view_proper_times) {
string str = hr::format(tformat, cd.center.shift / ads_time_unit);
queuestr(shiftless(rgpushxto0(cd.center.h)), time_scale * ads_scale, str, 0xFF4040, 8);
@ -180,20 +182,20 @@ void draw_game_cell(const cell_to_draw& cd) {
if(paused) for(auto& rock: ci.shipstates) {
cross_result cr;
if(hv) {
auto& shape = shape_ship;
for(int i=0; i<isize(shape); i += 2) {
auto h = twist::uxpush(shape[i] * ads_scale) * twist::uypush(shape[i+1] * ads_scale) * C0;
if(hv) render_ship_parts([&] (const hpcshape& sh, color_t col, int sym) {
int dx = sym ? -1 : 1;
for(int i=sh.s; i<sh.e; i++) {
auto h = twist::uxpush(cgi.hpc[i][0]) * twist::uypush(cgi.hpc[i][1] * dx) * C0;
curvepoint(h);
}
curvepoint_first();
ads_matrix S = current * V * rock.at;
S = S * spin(-(rock.ang+90)*degree);
apply_duality(S);
S = S * spin(+(rock.ang+90)*degree);
queuecurve(S, shipcolor, 0, PPR::LINE);
continue;
}
queuecurve(S, col, 0, PPR::LINE);
});
if(hv) continue;
hybrid::in_actual([&]{
dynamicval<eGeometry> b(geometry, gTwistedProduct);
@ -202,19 +204,21 @@ void draw_game_cell(const cell_to_draw& cd) {
});
if(cr.shift < -1e-6 || cr.shift > rock.duration + 1e-6) continue;
vector<hyperpoint> pts;
auto& shape = shape_ship;
for(int i=0; i<isize(shape); i += 2) {
hybrid::in_actual([&]{
auto h = V * rock.at * rgpushxto0(normalize(hyperpoint(shape[i] * ads_scale, shape[i+1] * ads_scale, 1, 0)));
pts.push_back(cross0(current * h).h);
});
}
render_ship_parts([&] (const hpcshape& sh, color_t col, int sym) {
int dx = sym ? -1 : 1;
vector<hyperpoint> pts;
for(int i=sh.s; i<sh.e; i++) {
auto& ac = cgi.hpc;
hybrid::in_actual([&]{
auto h = V * rock.at * rgpushxto0(normalize(hyperpoint(ac[i][0], ac[i][1] * dx, 1, 0)));
pts.push_back(cross0(current * h).h);
});
}
for(auto h: pts) curvepoint(h);
curvepoint(pts[0]);
queuecurve(shiftless(Id), 0xFF, shipcolor, PPR::MONSTER_FOOT);
for(auto h: pts) curvepoint(h);
queuecurve(shiftless(Id), 0xFF, col, PPR::MONSTER_FOOT);
});
if(view_proper_times) {
string str = hr::format(tformat, (cr.shift + rock.start) / ads_time_unit);
@ -356,12 +360,16 @@ void view_ads_game() {
ld u = (invincibility_pt-ship_pt) / ads_how_much_invincibility;
poly_outline = gradient(shipcolor, rsrc_color[rtHull], 0, 0.5 + cos(5*u*TAU), 1);
}
queuepolyat(shiftless(spin(ang*degree) * Id), make_shape(), shipcolor, PPR::MONSTER_HAIR);
render_ship_parts([&] (const hpcshape& sh, color_t col, int sym) {
shiftmatrix M = shiftless(spin(ang*degree) * Id);
if(sym) M = M * MirrorY;
queuepolyat(M, sh, col, PPR::MONSTER_HAIR);
});
poly_outline = 0xFF;
if(view_proper_times) {
string str = hr::format(tformat, ship_pt / ads_time_unit);
queuestr(shiftless(Id), time_scale * ads_scale, str, 0xFFFFFF, 8);
queuestr(shiftless(Id), time_scale * get_scale(), str, 0xFFFFFF, 8);
}
}
}

View File

@ -132,6 +132,7 @@ struct rock_generator {
void rack() {
report("Rack");
ld ds_scale = get_scale();
int qty = 3 + rand() % 4;
ld rapidity = rand_range(1, 3);
ld step = rand_range(.45, .75) * ds_scale;
@ -147,6 +148,7 @@ struct rock_generator {
void hyperboloid() {
report("Hyperboloid");
ld ds_scale = get_scale();
ld alpha = randd() * TAU;
ld range1 = rand_range(0.15, 0.25) * ds_scale;
ld range2 = rand_range(0.35, 0.45) * ds_scale;
@ -303,6 +305,7 @@ void ds_handle_crashes() {
}
if(!game_over) for(int i=0; i<isize(shape_ship); i+=2) {
ld ds_scale = get_scale();
hyperpoint h = spin(ang*degree) * hpxyz(shape_ship[i] * ds_scale, shape_ship[i+1] * ds_scale, 1);
for(auto r: drocks) {
if(pointcrash(h, r->pts)) ds_crash_ship();
@ -364,6 +367,7 @@ bool ds_turn(int idelta) {
}
if(!paused) {
ld ds_scale = get_scale();
pdata.fuel -= dv;
ds_gen_particles(rpoisson(dv*fuel_particle_qty), inverse(current.T) * spin(ang*degree+M_PI) * twist::uxpush(0.06 * ds_scale), current.shift, rsrc_color[rtFuel], fuel_particle_rapidity, fuel_particle_life, 0.02);
}
@ -477,8 +481,13 @@ cross_result ds_cross0_light(transmatrix T) {
return res;
}
transmatrix tpt_scaled(ld x, ld y) {
return cspin(0, 2, x) * cspin(1, 2, y);
}
transmatrix tpt(ld x, ld y) {
return cspin(0, 2, x * ds_scale) * cspin(1, 2, y * ds_scale);
ld ds_scale = get_scale();
return tpt_scaled(x * ds_scale, y * ds_scale);
}
// sometimes the result may be incorrect due to numerical precision -- don't show that then in this case
@ -582,6 +591,7 @@ void view_ds_game() {
if(view_proper_times && rock.type != oParticle) {
ld t = rock.pt_main.shift;
ld ds_scale = get_scale();
if(rock.type == oMainRock) t += current.shift;
string str = hr::format(tformat, t / ds_time_unit);
queuestr(shiftless(sphereflip * rgpushxto0(rock.pt_main.h)), time_scale * ds_scale, str, 0xFFFF00, 8);
@ -597,46 +607,52 @@ void view_ds_game() {
if(ss.at.shift < current.shift - 4 * TAU) continue;
if(ss.at.shift > current.shift + 4 * TAU) continue;
auto& shape = shape_ship;
if(hv) {
for(int i=0; i<isize(shape); i+=2) {
hyperpoint h = hvrel ? tpt(shape[i], shape[i+1]) * pov: hpxy(shape[i], shape[i+1]);
if(hv) render_ship_parts([&] (const hpcshape& sh, color_t col, int sym) {
int dx = sym ? -1 : 1;
for(int i=sh.s; i<sh.e; i ++) {
auto x = cgi.hpc[i][0];
auto y = cgi.hpc[i][1]*dx;
hyperpoint h = hvrel ? tpt_scaled(x, y) * pov: hpxy(x, y);
curvepoint(h);
}
curvepoint_first();
shiftmatrix S = shiftless(current.T * lorentz(2, 3, ss.at.shift - current.shift) * ss.at.T);
queuecurve(S, shipcolor, 0, PPR::TRANSPARENT_WALL);
}
queuecurve(S, col, 0, PPR::TRANSPARENT_WALL);
});
dynamicval<eGeometry> g(geometry, gSpace435);
cross_result cr = ds_cross0(current.T * lorentz(2, 3, ss.at.shift - current.shift) * ss.at.T);
if(cr.shift < delta) continue;
if(cr.shift > ss.duration + delta) continue;
transmatrix at = current.T * lorentz(2, 3, cr.shift) * ss.at.T;
vector<hyperpoint> pts;
for(int i=0; i<isize(shape); i += 2) {
transmatrix at1 = at * tpt(shape[i], shape[i+1]);
pts.push_back(ds_cross0(at1).h);
}
geometry = g.backup;
render_ship_parts([&] (const hpcshape& sh, color_t col, int sym) {
geometry = gSpace435;
vector<hyperpoint> pts;
int dx = sym ? -1 : 1;
for(int i=sh.s; i<sh.e; i ++) {
transmatrix at1 = at * tpt_scaled(cgi.hpc[i][0], cgi.hpc[i][1]*dx);
pts.push_back(ds_cross0(at1).h);
}
geometry = g.backup;
if(!hv) {
for(auto pt: pts) curvepoint(pt);
queuecurve(shiftless(sphereflip), 0xFF, shipcolor, PPR::MONSTER_FOOT);
}
if(!hv) {
for(auto pt: pts) curvepoint(pt);
queuecurve(shiftless(sphereflip), 0xFF, col, PPR::MONSTER_FOOT);
}
if(pmodel == mdPerspective) {
for(auto pt: pts) curvepoint(pt);
curvepoint_first();
queuecurve(shiftless(sphereflip), ghost_color, 0, PPR::MONSTER_FOOT).flags |= POLY_NO_FOG | POLY_FORCEWIDE;
}
if(pmodel == mdPerspective) {
if(col == shipcolor) col = ghost_color;
for(auto pt: pts) curvepoint(pt);
queuecurve(shiftless(sphereflip), col, 0, PPR::MONSTER_FOOT).flags |= POLY_NO_FOG | POLY_FORCEWIDE;
}
});
if(view_proper_times) {
string str = hr::format(tformat, (cr.shift + ss.start) / ds_time_unit);
ld ds_scale = get_scale();
queuestr(shiftless(sphereflip * rgpushxto0(cr.h)), time_scale * ds_scale, str, 0xC0C0C0, 8);
}
}
@ -647,21 +663,25 @@ void view_ds_game() {
ld u = (invincibility_pt-ship_pt) / ds_how_much_invincibility;
poly_outline = gradient(shipcolor, rsrc_color[rtHull], 0, 0.5 + cos(5*u*TAU), 1);
}
if(hv) {
auto& shape = shape_ship;
for(int i=0; i<isize(shape); i += 2) {
transmatrix at1 = tpt(shape[i], shape[i+1]);
curvepoint(ds_cross0(at1).h);
render_ship_parts([&] (const hpcshape& sh, color_t col, int sym) {
if(hv) {
int dx = sym ? -1 : 1;
for(int i=sh.s; i<sh.e; i++) {
transmatrix at1 = tpt_scaled(cgi.hpc[i][0], cgi.hpc[i][1] * dx);
curvepoint(ds_cross0(at1).h);
}
queuecurve(shiftless(sphereflip * spin(ang*degree)), col, 0, PPR::MONSTER_HAIR).flags |= POLY_NO_FOG | POLY_FORCEWIDE;
}
curvepoint_first();
queuecurve(shiftless(sphereflip * spin(ang*degree)), ghost_color, 0, PPR::MONSTER_HAIR).flags |= POLY_NO_FOG | POLY_FORCEWIDE;
}
else {
queuepolyat(shiftless(sphereflip * spin(ang*degree)), make_shape(), shipcolor, PPR::MONSTER_HAIR);
}
else {
shiftmatrix M = shiftless(sphereflip * spin(ang*degree));
if(sym) M = M * MirrorY;
queuepolyat(M, sh, col, PPR::MONSTER_HAIR);
}
});
poly_outline = 0xFF;
if(view_proper_times) {
ld ds_scale = get_scale();
string str = hr::format(tformat, ship_pt / ds_time_unit);
queuestr(shiftless(sphereflip), time_scale * ds_scale, str, 0xFFFFFF, 8);
}
@ -697,6 +717,7 @@ void ds_restart() {
main_rock = nullptr;
if(true) {
ld ds_scale = get_scale();
dynamicval<eGeometry> g(geometry, gSpace435);
current = cspin(0, 2, 0.2 * ds_scale);
invincibility_pt = ds_how_much_invincibility;
@ -724,6 +745,7 @@ void run_ds_game_hooks() {
void run_ds_game() {
stop_game();
run_size_hooks();
set_geometry(gSphere);
start_game();

View File

@ -65,10 +65,6 @@ void change_scale(ld s);
/** all the missiles and objects currently displayed */
vector<struct ads_object*> displayed;
/** how much should be the objects scaled */
ld ads_scale = 1;
ld ds_scale = 1;
ld time_scale = .1;
color_t missile_color = 0xFF0000FF;
@ -111,6 +107,8 @@ cell *starting_point;
int max_gen_per_frame = 3;
int draw_per_frame = 200;
bool simple_ship = false;
/* for DS */
ads_object *main_rock;
@ -126,6 +124,7 @@ void init_textures();
void pick_textures();
void draw_textures();
void reset_textures();
void run_size_hooks();
void ds_restart();
void run_ads_game_std();

View File

@ -234,7 +234,7 @@ void gen_rocks(cell *c, cellinfo& ci, int radius) {
hybrid::in_actual([&] {
add_turret(c, ci, p.get());
for(int r=0; r<6; r++)
add_rsrc(c, ci, p.get(spin(r * 60._deg) * twist::uxpush(turret_dist * ads_scale)));
add_rsrc(c, ci, p.get(spin(r * 60._deg) * twist::uxpush(turret_dist * get_scale())));
turrets++;
});
}
@ -410,7 +410,7 @@ void handle_turret(ads_object *t, ld& angle_at_time) {
if(t->last_shot >= t->life_end) return;
ld angle = lerp(it0->second.angle, nts.angle, ilerp(it0->first, ctime, t->last_shot));
// println(hlog, "shooting at angle ", angle, " at time ", t->last_shot);
ads_matrix S0 = ads_inverse(p->V) * t1 * spin(angle) * twist::uxpush(turret_length * ads_scale) * lorentz(0, 2, ads_missile_rapidity);
ads_matrix S0 = ads_inverse(p->V) * t1 * spin(angle) * twist::uxpush(turret_length * get_scale()) * lorentz(0, 2, ads_missile_rapidity);
auto r = std::make_unique<ads_object> (oTurretMissile, t->owner, S0, rsrc_color[rtAmmo]);
r->shape = &shape_missile;
r->life_start = 0; r->life_end = M_PI;
@ -464,6 +464,7 @@ void handle_crashes() {
}
}
if(!game_over) for(int i=0; i<isize(shape_ship); i+=2) {
ld ads_scale = get_scale();
hyperpoint h = spin(ang*degree) * hpxyz(shape_ship[i] * ads_scale, shape_ship[i+1] * ads_scale, 1);
for(auto r: rocks) {
if(pointcrash(h, r->pts)) ads_crash_ship();

View File

@ -3,6 +3,7 @@ namespace hr {
namespace ads_game {
void adjust_for_scale() {
ld ads_scale = get_scale();
if(ads_scale < 0.3) max_gen_per_frame = 1, draw_per_frame = 30;
else if(ads_scale < 0.8) max_gen_per_frame = 2, draw_per_frame = 100;
else max_gen_per_frame = 3, draw_per_frame = 1000;
@ -16,7 +17,7 @@ void edit_difficulty() {
add_edit(DS_(simspeed));
add_edit(DS_(accel));
add_edit(DS_(how_much_invincibility));
add_edit(DS_(scale));
add_edit(vid.creature_scale);
add_edit(DS_(missile_rapidity));
if(!main_rock) {

View File

@ -21,12 +21,17 @@ struct ship_model: gi_extension {
map<ld, hpcshape> ship_at_scale;
};
/** how much should be the objects scaled */
ld get_scale() {
return cgi.scalefactor * 3;
}
const hpcshape& make_shape() {
auto& mmd = (unique_ptr<ship_model>&) cgi.ext["ship_model"];
if(!mmd) mmd = std::make_unique<ship_model> ();
auto scale = DS_(scale);
auto scale = get_scale();
auto sas = at_or_null(mmd->ship_at_scale, scale);
if(sas) return *sas;
@ -46,4 +51,20 @@ const hpcshape& make_shape() {
return shShip;
}
extern color_t shipcolor;
template<class T> void render_ship_parts(const T& render_ship_part) {
if(simple_ship) {
render_ship_part(make_shape(), shipcolor, 0);
return;
}
charstyle& cs = getcs();
render_ship_part(cgi.shSpaceshipBase, cs.skincolor, 0);
render_ship_part(cgi.shSpaceshipEngine, cs.haircolor, 0);
render_ship_part(cgi.shSpaceshipEngine, cs.haircolor, 1);
render_ship_part(cgi.shSpaceshipGun, cs.dresscolor, 0);
render_ship_part(cgi.shSpaceshipGun, cs.dresscolor, 1);
render_ship_part(cgi.shSpaceshipCockpit, cs.eyecolor, 0);
}
}}

View File

@ -79,6 +79,15 @@ void straight_line_viz(presmode mode) {
});
}
void set_spacerocks_ship() {
auto& cs = getcs();
tour::slide_backup(cs.charid, 10);
tour::slide_backup(cs.skincolor, 0xFFFFFFFF);
tour::slide_backup(cs.eyecolor, 0x8080FFFF);
tour::slide_backup(cs.dresscolor, 0xFFC0C0FF);
tour::slide_backup(cs.haircolor, 0xC0FFC0FF);
}
slide relhell_tour[] = {
{"Intro", 10, LEGAL::ANY | QUICKGEO | NOTITLE,
"Relative Hell is a game taking place in relativistic analogs of spherical and hyperbolic geometries. "
@ -86,6 +95,7 @@ slide relhell_tour[] = {
"if you accelerate, you move forever in that direction, unless you deaccelerate.",
[] (presmode mode) {
setCanvas(mode, &ccolor::plain, [] {
set_spacerocks_ship();
set_geometry(gEuclidSquare);
set_variation(eVariation::pure);
tour::slide_backup(land_structure, lsSingle);
@ -111,7 +121,7 @@ slide relhell_tour[] = {
const ld sca = 100;
tour::slide_backup(ds_simspeed, M_PI / 10 / sca * 5);
tour::slide_backup(ds_missile_rapidity, 0.1);
tour::slide_backup(ds_scale, 1 / sca);
tour::slide_backup(vid.creature_scale, vid.creature_scale / sca);
tour::slide_backup(pconf.scale, sca);
tour::slide_backup(texture_off, true);
dynamicval<ld> fs(future_shown, -10);
@ -149,7 +159,7 @@ slide relhell_tour[] = {
tour::slide_backup(ds_simspeed, M_PI / 10 / sca * 5);
tour::slide_backup(ds_missile_rapidity, 0.5);
tour::slide_backup(ds_accel, ds_accel * 10);
tour::slide_backup(ds_scale, 1 / sca);
tour::slide_backup(vid.creature_scale, vid.creature_scale / sca);
tour::slide_backup(pconf.scale, sca);
tour::slide_backup(texture_off, true);
dynamicval<ld> fs(future_shown, -10);
@ -190,7 +200,7 @@ slide relhell_tour[] = {
tour::slide_backup(ds_simspeed, M_PI / 10 / sca * 5);
tour::slide_backup(ds_missile_rapidity, 0.5);
tour::slide_backup(ds_accel, ds_accel * 10);
tour::slide_backup(ds_scale, 5 / sca);
tour::slide_backup(vid.creature_scale, vid.creature_scale * 5 / sca);
tour::slide_backup(pconf.scale, sca);
tour::slide_backup(texture_off, true);
tour::slide_backup(view_proper_times, true);
@ -212,6 +222,7 @@ slide relhell_tour[] = {
"stereographic projection so that a big part of the sphere can be seen. (You can press '5' to switch to and from the orthogonal projection.)",
[] (presmode mode) {
setCanvas(mode, &ccolor::plain, [] {
set_spacerocks_ship();
set_geometry(gSphere);
set_variation(eVariation::bitruncated);
tour::slide_backup(land_structure, lsSingle);
@ -244,6 +255,7 @@ slide relhell_tour[] = {
[] (presmode mode) {
setCanvas(mode, &ccolor::plain, [] {
set_spacerocks_ship();
set_geometry(gSphere);
set_variation(eVariation::bitruncated);
tour::slide_backup(land_structure, lsSingle);
@ -271,7 +283,7 @@ slide relhell_tour[] = {
setCanvas(mode, &ccolor::plain, [] {
ads_game::run_ds_game_std();
tour::slide_backup(ds_simspeed, M_PI / 10);
tour::slide_backup(ds_scale, 1);
// tour::slide_backup(ds_scale, 1);
tour::slide_backup(pconf.scale, 1);
dynamicval<ld> fs(future_shown, -10);
ds_restart();
@ -309,6 +321,7 @@ slide relhell_tour[] = {
[] (presmode mode) {
setCanvas(mode, &ccolor::plain, [] {
set_spacerocks_ship();
set_geometry(gKleinQuartic);
set_variation(eVariation::bitruncated);
tour::slide_backup(land_structure, lsSingle);
@ -331,6 +344,7 @@ slide relhell_tour[] = {
[] (presmode mode) {
setCanvas(mode, &ccolor::plain, [] {
set_spacerocks_ship();
set_geometry(gKleinQuartic);
set_variation(eVariation::bitruncated);
tour::slide_backup(land_structure, lsSingle);