1 // Hyperbolic Rogue -- the 'experiments with geometry' menu
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file geom-exp.cpp
5  *  \brief The 'experiments with geometry' menu
6  *
7  *  Implementation of this menu, and computation of the statistics shown there
8  */
9 
10 #include "hyper.h"
11 namespace hr {
12 
13 int eupage = 0;
14 int euperpage = 9;
15 
16 string euchelp =
17   "If you want to know how much the gameplay is affected by the "
18   "hyperbolic geometry in HyperRogue, this mode is for you!\n\n"
19 
20   "You can try many different geometries here. We start by gluing "
21   "n-gons in such a way that k of them meet in every vertex. "
22   "Depending on n and k, this either folds into a sphere, unfolds into a plane, "
23   "or requires a hyperbolic space. The result may be then 'bitruncated' by "
24   "replacing each vertex by a 2k-gon. Furthermore, you can play "
25   "with quotient geometries. For example, the elliptic geometry is "
26   "obtained from the sphere by making the antipodes be the same point, "
27   "so you return to the same spot (but as a mirror image) after going there. "
28   "Have fun experimenting! "
29   "Achievements and leaderboards do not work in geometry experiments, "
30   "except some specific ones.\n\n"
31   "In standard geometry (bitruncated or not), you can play the full game, but in other geometries "
32   "you select a particular land. Lands are unlocked by visiting them in this "
33   "session, or permanently by collecting 25 treasure. Try Crossroads in Euclidean "
34   "or chaos mode in non-standard non-quotient hyperbolic to visit many lands. "
35   "Highlights:\n"
36   "* Crystal World and Warped Coast can be understood as extra geometries.\n"
37   "* Halloween is specially designed for spherical geometry.\n"
38   "* To see the difference, try Hunting Grounds in Euclidean -- it is impossible.\n";
39 
40 #if CAP_FIELD
showQuotientConfig()41 void showQuotientConfig() {
42   using namespace fieldpattern;
43   gamescreen(2);
44   dialog::init(XLAT("field quotient"));
45   fgeomextra& gxcur = fgeomextras[current_extra];
46   for(int i=0; i<isize(fgeomextras); i++) {
47     auto& g = fgeomextras[i];
48     dialog::addBoolItem(ginf[g.base].tiling_name, g.base == gxcur.base, 'a'+i);
49     dialog::add_action([i] { current_extra = i; });
50     }
51 
52   dialog::addBreak(100);
53 
54   nextPrimes(gxcur);
55   string stars[3] = {"", "*", "**"};
56   for(int i=0; i<isize(gxcur.primes); i++) {
57     auto& p = gxcur.primes[i];
58 
59     int sides = ginf[gxcur.base].sides;
60     bool sa = shapename_available(sides);
61     string s = sa ? "order %1%2 (%3 %4s)" : "order %1%2 (%3 %4-gons)";
62 
63     s = XLAT(s, its(p.p), p.squared ? "²" : "", its(p.cells), sa ? shapename(sides) : its(sides));
64 
65     s += stars[gxcur.dualval[i]];
66 
67     dialog::addBoolItem(s, i == gxcur.current_prime_id, 'A'+i);
68     dialog::add_action([&gxcur, i] { gxcur.current_prime_id = i; });
69     }
70 
71   if(isize(gxcur.primes) <= 6) {
72     dialog::addBreak(100);
73     dialog::addHelp(
74       "This geometry is obtained by applying the same 'generators' which "
75       "lead to creating the given basic hyperbolic geometry, "
76       "but using a fixed finite field instead of the field of reals. "
77       "It can be also interpreted as a quotient of the given basic geometry. "
78       "Warning: field patterns based on large primes might generate for a long time."
79       );
80     dialog::addBreak(100);
81     }
82 
83   dialog::addItem("find the next prime", 'p');
84   dialog::add_action([&gxcur] { nextPrime(gxcur); });
85 
86   dialog::addItem("activate", 'x');
87   dialog::add_action_confirmed([&gxcur] {
88     set_geometry(gxcur.base);
89     enableFieldChange();
90     set_geometry(gFieldQuotient);
91     start_game();
92     });
93 
94   dialog::addItem("find alternate manifolds", 'y');
95   dialog::add_action_confirmed([&gxcur] {
96     set_geometry(gxcur.base);
97     triplet_id = 0;
98     enableFieldChange();
99     set_geometry(gFieldQuotient);
100     start_game();
101 
102     auto& cfp = currfp;
103     auto triplets = cfp.find_triplets();
104     pushScreen([triplets] {
105       gamescreen(2);
106       dialog::init(XLAT("alternate manifolds"));
107       int id = 0;
108       for(auto t: triplets) {
109         dialog::addItem(XLAT("generators (%1,%2), size %3", its(t.i), its(t.j), its(t.size/S7)), 'a'+id);
110         dialog::add_action([id] {
111           stop_game();
112           triplet_id = id;
113           fieldpattern::enableFieldChange();
114           start_game();
115           });
116         id++;
117         }
118       dialog::addBreak(100);
119       dialog::addHelp(XLAT(
120         "This option finds alternate solutions. For example, there are three {7,3} manifolds with 156 heptagons each (\"first Hurwitz triplet\").")
121         );
122       dialog::display();
123       });
124     });
125 
126   dialog::addItem("default", 'z');
127   dialog::add_action_confirmed([] {
128     set_geometry(gEuclid);
129     fieldpattern::quotient_field_changed = false;
130     set_geometry(gFieldQuotient);
131     start_game();
132     });
133 
134   dialog::addBack();
135   dialog::display();
136   }
137 #endif
138 
139 EX string bitruncnames[5] = {" (b)", " (n)", " (g)", " (i)", " (d)"};
140 
validity_info()141 void validity_info() {
142   int vccolors[4] = {0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00};
143   auto lv = land_validity(specialland);
144   if(lv.flags & lv::display_error_message)
145     dialog::addInfo(XLAT(lv.msg), vccolors[lv.quality_level]);
146   else
147     dialog::addBreak(100);
148   }
149 
150 EX bool showquotients;
151 
152 string validclasses[4] = {" (X)", " (½)", "", " (!)"};
153 
ge_land_selection()154 EX void ge_land_selection() {
155   cmode = sm::SIDE | sm::MAYDARK;
156   gamescreen(0);
157 
158   if(cheater) for(int i=0; i<landtypes; i++) landvisited[i] = true;
159 
160   for(int i=0; i<landtypes; i++)
161     if(hiitemsMax(treasureType(eLand(i))) >= 25) landvisited[i] = true;
162   landvisited[laCrossroads] = true;
163   landvisited[laCrossroads4] = true;
164   landvisited[laIce] = true;
165   landvisited[laHunting] = true;
166   landvisited[laMirrorOld] = true;
167   landvisited[laPrincessQuest] = cheater || princess::everSaved;
168   landvisited[laWildWest] = true;
169   landvisited[laHalloween] = true;
170   landvisited[laWarpCoast] = true;
171   landvisited[laGraveyard] = true;
172   landvisited[laDual] = true;
173   landvisited[laDocks] |= landvisited[laWarpCoast];
174   landvisited[laSnakeNest] |= landvisited[laRedRock];
175   landvisited[laCamelot] |= hiitemsMax(treasureType(laCamelot)) >= 1;
176   landvisited[laCA] = true;
177   landvisited[laAsteroids] = true;
178 
179   dialog::init(XLAT("select the starting land"));
180   if(dialog::infix != "") mouseovers = dialog::infix;
181 
182   generateLandList([] (eLand l) {
183     if(dialog::infix != "" && !dialog::hasInfix(linf[l].name)) return false;
184     return !!(land_validity(l).flags & lv::appears_in_geom_exp);
185     });
186   stable_sort(landlist.begin(), landlist.end(), [] (eLand l1, eLand l2) { return land_validity(l1).quality_level > land_validity(l2).quality_level; });
187 
188   for(int i=0; i<euperpage; i++) {
189     if(euperpage * eupage + i >= isize(landlist)) { dialog::addBreak(100); break; }
190     eLand l = landlist[euperpage * eupage + i];
191     char ch = '1'+i;
192     string s = XLAT1(linf[l].name);
193 
194     if(landvisited[l]) {
195       dialog::addBoolItem(s, l == specialland, ch);
196       }
197     else {
198       dialog::addSelItem(s, XLAT("(locked)"), ch);
199       }
200 
201     dialog::lastItem().color = linf[l].color;
202     dialog::lastItem().value += validclasses[land_validity(l).quality_level];
203     dialog::add_action([l] {
204       if(landvisited[l]) dialog::do_if_confirmed(dual::mayboth([l] {
205         stop_game_and_switch_mode(tactic::on ? rg::tactic : rg::nothing);
206         firstland = specialland = l;
207         if(l == laCanvas || l == laAsteroids)
208           land_structure = lsSingle;
209         else if(among(l, laCrossroads, laCrossroads2))
210           land_structure = lsNiceWalls;
211         else if(among(l, laCrossroads4))
212           land_structure = lsNoWalls;
213         start_game();
214         popScreen();
215         }));
216       });
217     }
218   dialog::addItem(XLAT("next page"), '-');
219   dialog::addInfo(XLAT("press letters to search"));
220 
221   dialog::addBreak(25);
222   validity_info();
223   dialog::addBreak(25);
224 
225   dual::add_choice();
226   dialog::addBack();
227   dialog::display();
228 
229   keyhandler = [] (int sym, int uni) {
230     dialog::handleNavigation(sym, uni);
231 
232     if(uni == '-' || uni == PSEUDOKEY_WHEELUP || uni == PSEUDOKEY_WHEELDOWN) {
233       eupage++;
234       if(eupage * euperpage >= isize(landlist)) eupage = 0;
235       }
236     else if(dialog::editInfix(uni)) eupage = 0;
237     else if(doexiton(sym, uni)) popScreen();
238     };
239   }
240 
activate_ge_land_selection()241 EX void activate_ge_land_selection() {
242   dialog::infix = "";
243   eupage = 0;
244   pushScreen(ge_land_selection);
245   }
246 
247 #if HDR
248 struct geometry_filter {
249   string name;
250   /** test if the current geometry matches the filter */
251   function<bool()> test;
252   };
253 #endif
254 
255 EX geometry_filter *current_filter;
256 
forced_quotient()257 bool forced_quotient() { return quotient && !(cgflags & qOPTQ); }
258 
__anon29482a1f0e02null259 EX geometry_filter gf_hyperbolic = {"hyperbolic", [] { return (arcm::in() || arb::in() || hyperbolic) && !forced_quotient(); }};
__anon29482a1f0f02null260 EX geometry_filter gf_spherical = {"spherical", [] { return (arcm::in() || arb::in() || sphere) && !forced_quotient(); }};
__anon29482a1f1002null261 EX geometry_filter gf_euclidean = {"Euclidean", [] { return (arcm::in() || arb::in() || euclid) && !forced_quotient(); }};
__anon29482a1f1102null262 EX geometry_filter gf_other = {"non-isotropic", [] { return prod || nonisotropic; }};
__anon29482a1f1202null263 EX geometry_filter gf_regular_2d = {"regular 2D tesselations", [] {
264   return standard_tiling() && WDIM == 2 && !forced_quotient();
265   }};
__anon29482a1f1302null266 EX geometry_filter gf_regular_3d = {"regular 3D honeycombs", [] {
267   if(euclid) return geometry == gCubeTiling;
268   return !bt::in() && !kite::in() && WDIM == 3 && !forced_quotient() && !nonisotropic && !prod;
269   }};
__anon29482a1f1402null270 EX geometry_filter gf_quotient = {"interesting quotient spaces", [] {
271   return forced_quotient() && !elliptic;
272   }};
273 
274 EX vector<geometry_filter*> available_filters = { &gf_hyperbolic, &gf_spherical, &gf_euclidean, &gf_other, &gf_regular_2d, &gf_regular_3d, &gf_quotient };
275 
ge_select_filter()276 void ge_select_filter() {
277   cmode = sm::SIDE | sm::MAYDARK;
278   gamescreen(2);
279 
280   dialog::init(XLAT("geometries"));
281 
282   char x = 'a';
283   for(auto f: available_filters) {
284     if(current_filter)
285       dialog::addBoolItem(XLAT(f->name), f == current_filter, x++);
286     else
287       dialog::addItem(XLAT(f->name), x++);
288     dialog::add_action([f] { current_filter = f; popScreen(); });
289     }
290 
291   dialog::addBack();
292   dialog::display();
293   }
294 
set_default_filter()295 void set_default_filter() {
296   current_filter = hyperbolic ? &gf_hyperbolic : euclid ? &gf_euclidean : sphere ? &gf_spherical : &gf_other;
297   for(auto f: available_filters) if(f->test()) current_filter = f;
298   }
299 
set_or_configure_geometry(eGeometry g)300 void set_or_configure_geometry(eGeometry g) {
301   if(0) ;
302   #if CAP_CRYSTAL
303   else if(g == gCrystal)
304     pushScreen(crystal::show);
305   #endif
306   #if CAP_ARCM
307   else if(g == gArchimedean)
308     pushScreen(arcm::show);
309   #endif
310   else if(g == gArbitrary)
311     arb::choose();
312   else {
313     if(among(g, gProduct, gRotSpace)) {
314       if(WDIM == 3 || (g == gRotSpace && euclid)) {
315         addMessage(
316           g == gRotSpace ?
317             XLAT("Only works with 2D non-Euclidean geometries")
318           : XLAT("Only works with 2D geometries")
319             );
320         return;
321         }
322       if(g == gRotSpace) {
323         bool ok = true;
324         if(arcm::in()) ok = PURE;
325         else if(bt::in() || kite::in()) ok = false;
326         else ok = PURE || BITRUNCATED;
327         if(!ok) {
328           addMessage(XLAT("Only works with (semi-)regular tilings"));
329           return;
330           }
331         #if CAP_ARCM
332         if(arcm::in()) {
333           int steps, single_step;
334           if(!arcm::current.get_step_values(steps, single_step)) {
335             addMessage(XLAT("That would have %1/%2 levels", its(steps), its(single_step)));
336             return;
337             }
338           }
339         #endif
340         }
341       }
342     dual::may_split_or_do([g] { set_geometry(g); });
343     start_game();
344     }
345   }
346 
347 /** is g2 the same tiling as the current geometry (geometry)? */
same_tiling(eGeometry g2)348 bool same_tiling(eGeometry g2) {
349   if(g2 == gCrystal)
350     return S3 == 4;
351   if(g2 == gFieldQuotient && hyperbolic && standard_tiling())
352     return true;
353   if(g2 == gFieldQuotient && geometry != gFieldQuotient) {
354     int ce = 0;
355     for(auto& ge: fieldpattern::fgeomextras) {
356       if(ginf[ge.base].tiling_name == ginf[geometry].tiling_name) {
357         fieldpattern::current_extra = ce;
358         return true;
359         }
360       ce++;
361       }
362     }
363   return ginf[g2].tiling_name == ginf[geometry].tiling_name;
364   }
365 
ge_select_tiling()366 void ge_select_tiling() {
367   cmode = sm::SIDE | sm::MAYDARK;
368   gamescreen(0);
369 
370   if(!current_filter) { popScreen(); return; }
371   dialog::init();
372   dialog::addItem(XLAT(current_filter->name), 'x');
373   dialog::add_action_push(ge_select_filter);
374 
375   vector<eGeometry> geometries;
376 
377   dialog::addBreak(100);
378 
379   char letter = 'a';
380   for(int i=0; i<isize(ginf); i++) {
381     eGeometry g = eGeometry(i);
382     if(among(g, gProduct, gRotSpace)) hybrid::configure(g);
383     bool orig_el = elliptic;
384     bool on = geometry == g;
385     bool in_2d = WDIM == 2;
386     dynamicval<eGeometry> cg(geometry, g);
387     if(cgflags & qDEPRECATED) continue;
388     if(arcm::in() && !CAP_ARCM) continue;
389     if(cryst && !CAP_CRYSTAL) continue;
390     if(sol && !CAP_SOLV) continue;
391     if(arb::in() && (ISMOBILE || ISWEB)) continue;
392     if(WDIM == 3 && MAXMDIM == 3) continue;
393     if(geometry == gFieldQuotient && !CAP_FIELD) continue;
394     if(geometry == gFake) continue;
395     if(!current_filter->test()) continue;
396     if(orig_el) {
397       for(int j=0; j<isize(ginf); j++)
398         if(ginf[j].tiling_name == ginf[i].tiling_name)
399           geometry = g = eGeometry(j);
400       }
401 
402     bool is_product = (geometry == gProduct && in_2d);
403     bool is_rotspace = (geometry == gRotSpace && in_2d);
404     dialog::addBoolItem(
405       is_product ? XLAT("current geometry x E") :
406       is_rotspace ? XLAT("space of rotations in current geometry") :
407       XLAT(ginf[g].menu_displayed_name), on, letter++);
408     dialog::lastItem().value += validclasses[land_validity(specialland).quality_level];
409     dialog::add_action([g] { set_or_configure_geometry(g); });
410     }
411 
412   dialog::addBreak(100);
413   dual::add_choice();
414   dialog::addBack();
415   dialog::display();
416   }
417 
current_proj_name()418 EX string current_proj_name() {
419   bool h = hyperbolic || sn::in();
420   if(vpconf.model == mdPanini && vpconf.alpha == 1)
421     return XLAT("stereographic Panini");
422   else if(vpconf.model != mdDisk)
423     return models::get_model_name(vpconf.model);
424   else if(h && vpconf.alpha == 1)
425     return XLAT("Poincaré model");
426   else if(h && vpconf.alpha == 0)
427     return XLAT("Klein-Beltrami model");
428   else if(h && vpconf.alpha == -1)
429     return XLAT("inverted Poincaré model");
430   else if(sphere && vpconf.alpha == 1)
431     return XLAT("stereographic projection");
432   else if(sphere && vpconf.alpha == 0)
433     return XLAT("gnomonic projection");
434   else if(sphere && vpconf.alpha >= 999)
435     return XLAT("orthographic projection");
436   else if(h && vpconf.alpha >= 999)
437     return XLAT("Gans model");
438   else
439     return XLAT("general perspective");
440   }
441 
dim_name()442 EX string dim_name() {
443   return " (" + its(WDIM) + "D)";
444   }
445 
446 #if CAP_THREAD && MAXMDIM >= 4
showQuotientConfig3()447 EX void showQuotientConfig3() {
448 
449   using namespace fieldpattern;
450   gamescreen(2);
451   dialog::init(XLAT("field quotient"));
452 
453   auto& ds = discoveries[cginf.tiling_name];
454 
455   if(!ds.discoverer) {
456     dialog::addItem("start discovery", 's');
457     dialog::add_action([&ds] { ds.activate(); });
458     }
459   else if(ds.is_suspended) {
460     dialog::addItem("resume discovery", 's');
461     dialog::add_action([&ds] { ds.activate(); });
462     }
463   else {
464     dialog::addItem("suspend discovery", 's');
465     dialog::add_action([&ds] { ds.suspend(); });
466     }
467 
468   auto& e = ds.experiment;
469   if(!e.Prime)
470     dialog::addBreak(100);
471   else {
472     string s = its(e.Prime);
473     if(e.wsquare) s += "²";
474     dialog::addInfo(s);
475     }
476 
477   dialog::addBreak(100);
478 
479   if(1) {
480     std::unique_lock<std::mutex> lk(ds.lock);
481     auto&l = ds.hashes_found;
482     for(auto& v: l) {
483       char x = 'a';
484       string s = XLAT("#%1, cells: %2, p=%3", itsh(v.first), its(get<5>(v.second)), its(get<0>(v.second)) + (get<1>(v.second) ? "²" : ""));
485       dialog::addItem(s, x++);
486       dialog::add_action([&v] {
487         stop_game();
488         int tmp;
489         tie(currfp.Prime, currfp.wsquare, currfp.R, currfp.P, currfp.X, tmp) = v.second;
490         currfp.Field = currfp.wsquare ? currfp.Prime * currfp.Prime : currfp.Prime;
491         currfp.generate_all3();
492         currfp.analyze();
493         start_game();
494         });
495       }
496     }
497 
498   dialog::addBreak(100);
499 
500   dialog::addBack();
501 
502   dialog::display();
503   }
504 #endif
505 
geometry_name()506 EX string geometry_name() {
507   switch(ginf[geometry].cclass) {
508     case gcHyperbolic:
509       return XLAT("hyperbolic") + dim_name();
510 
511     case gcEuclid:
512       if(cgflags & qAFFINE)
513         return XLAT("affine") + dim_name();
514       return XLAT("flat") + dim_name();
515 
516     case gcSphere:
517       return XLAT("spherical") + dim_name();
518 
519     case gcSolNIH:
520 #if CAP_SOLV
521       switch(sn::geom()) {
522         case gSol:
523           return XLAT("Sol");
524         case gNIH:
525           return XLAT("hyperbolic (3:2)");
526         case gSolN:
527           return XLAT("Sol (3:2)");
528         default:
529           return "unknown";
530         }
531 #else
532       return XLAT("Sol");
533 #endif
534 
535     case gcNil:
536       return XLAT("Nil");
537 
538     case gcSL2:
539       return XLAT("~SL(2,R)~");
540 
541     case gcProduct:
542       return XLAT("%1 x E", PIU(geometry_name()));
543     }
544   return "?";
545   }
546 
select_quotient_screen()547 EX void select_quotient_screen() {
548   cmode = sm::SIDE | sm::MAYDARK;
549   gamescreen(0);
550 
551   dialog::init(XLAT("quotient spaces in ") + ginf[geometry].tiling_name);
552   char key = 'a';
553   for(int i=0; i<isize(ginf); i++) {
554     auto g = eGeometry(i);
555     if(ginf[g].flags & qDEPRECATED) continue;
556     if(same_tiling(g)) {
557       dialog::addBoolItem(
558         (ginf[g].flags & qANYQ) ?
559            XLAT(ginf[g].menu_displayed_name) :
560            "no quotient",
561         g == geometry, key++);
562       dialog::add_action([g] {
563         if(g == gFieldQuotient && WDIM == 3) {
564           if(geometry != gFieldQuotient) {
565             stop_game();
566             fieldpattern::field_from_current();
567             set_geometry(gFieldQuotient);
568             for(int p=2;; p++) { currfp.Prime = p; currfp.force_hash = 0; if(!currfp.solve()) break; }
569             println(hlog, "set prime = ", currfp.Prime);
570             start_game();
571             }
572           #if CAP_THREAD && MAXMDIM >= 4
573           pushScreen(showQuotientConfig3);
574           #endif
575           }
576         else if(g == gFieldQuotient)
577           pushScreen(showQuotientConfig);
578         #if CAP_CRYSTAL
579         else if(g == gCrystal)
580           pushScreen(crystal::show);
581         #endif
582         else {
583           dual::may_split_or_do([g] { set_geometry(g); });
584           start_game();
585           }
586         });
587       }
588     }
589 
590   dialog::addBack();
591   dialog::display();
592   }
593 
select_quotient()594 EX void select_quotient() {
595   if(euclid && !kite::in() && !arcm::in()) {
596     euc::prepare_torus3();
597     pushScreen(euc::show_torus3);
598     }
599   else if(nil) {
600     nilv::prepare_niltorus3(),
601     pushScreen(nilv::show_niltorus3);
602     }
603   #if CAP_SOLV
604   else if(asonov::in()) {
605     asonov::prepare_config();
606     pushScreen(asonov::show_config);
607     }
608   #endif
609   else if(prod)
610     pushScreen(product::show_config);
611   else if(rotspace)
612     hybrid::configure_period();
613   else {
614     vector<eGeometry> choices;
615     for(int i=0; i<isize(ginf); i++) if(same_tiling(eGeometry(i))) choices.push_back(eGeometry(i));
616 
617     println(hlog, "choices = ", choices);
618 
619     if(isize(choices) > 1)
620       pushScreen(select_quotient_screen);
621     /* else if(isize(choices) > 1) {
622       set_geometry(choices[choices[0] == geometry ? 1 : 0]);
623       start_game();
624       } */
625     else
626       addMessage("No quotient spaces available in the current tiling.");
627     }
628   }
629 
full_geometry_name()630 EX string full_geometry_name() {
631   string qstring = ginf[geometry].quotient_name;
632   bool variable =
633     !(prod || hybri || bt::in() || (WDIM == 3 && !reg3::in()) || kite::in() || arb::in());
634 
635   string fgname = XLAT(ginf[geometry].tiling_name);
636   if(qstring != "none") fgname += " " + XLAT(qstring);
637   if(arcm::in()) fgname = arcm::current.symbol;
638   if(variable) fgname = gp::operation_name() + " " + fgname;
639   return fgname;
640   }
641 
action_change_variation()642 void action_change_variation() {
643   if(0) ;
644   #if CAP_ARCM
645   else if(arcm::in()) arcm::next_variation();
646   #endif
647   #if MAXMDIM >= 4
648   else if(reg3::in()) reg3::configure_variation();
649   #endif
650   else if(euc::in(2,4) || !CAP_GP) dialog::do_if_confirmed([] {
651     set_variation(PURE ? eVariation::bitruncated : eVariation::pure);
652     start_game();
653     });
654   #if CAP_GP
655   else // if(S3 == 3)
656     gp::configure();
657   #endif
658   }
659 
menuitem_change_variation(char key)660 EX void menuitem_change_variation(char key) {
661   dialog::addSelItem(XLAT("variations"), gp::operation_name(), key);
662   dialog::add_action(action_change_variation);
663   }
664 
menuitem_change_geometry(char key)665 EX void menuitem_change_geometry(char key) {
666   dialog::addSelItem(XLAT("geometry/topology/tiling"), full_geometry_name(), key);
667   dialog::add_action_push(current_filter ? ge_select_tiling : ge_select_filter);
668   }
669 
menuitem_projection(char key)670 EX void menuitem_projection(char key) {
671   dialog::addSelItem(XLAT("projection"), current_proj_name(), key);
672   dialog::add_action_push(models::model_menu);
673   }
674 
menuitem_binary_width(char key)675 EX void menuitem_binary_width(char key) {
676   dialog::addSelItem(XLAT("binary tiling width"), fts(vid.binary_width), key);
677   dialog::add_action([] {
678     dialog::editNumber(vid.binary_width, 0, 2, 0.1, 1, XLAT("binary tiling width"), "");
679     dialog::reaction = [] () {
680       #if CAP_TEXTURE
681       texture::config.remap();
682       #endif
683       #if CAP_SOLV
684       if(asonov::in()) asonov::prepare();
685       #endif
686       };
687     });
688   }
689 
menuitem_nilwidth(char key)690 EX void menuitem_nilwidth(char key) {
691   dialog::addSelItem(XLAT("Nil width"), fts(nilv::nilwidth), key);
692   dialog::add_action([] {
693     dialog::editNumber(nilv::nilwidth, 0.01, 2, 0.1, 1, XLAT("Nil width"), "");
694     dialog::reaction = ray::reset_raycaster;
695     dialog::bound_low(0.01);
696     });
697   }
698 
edit_stretch()699 EX void edit_stretch() {
700   stretch::mstretch = false;
701   ray::reset_raycaster();
702   dialog::editNumber(stretch::factor, -1, 9, 0.1, 0, XLAT("stretched geometry"),
703     XLAT(
704       "Stretch the metric along the fibers. This can currently be done in rotation spaces and in 8-cell, 24-cell and 120-cell. "
705       "Value of 0 means not stretched, -1 means S2xE or H2xE (works only in the limit). (Must be > -1)"
706       )
707     );
708   dialog::reaction = [] { if(abs(stretch::factor+1) < 1e-3) stretch::factor = -.9; ray::reset_raycaster(); };
709   }
710 
showEuclideanMenu()711 EX void showEuclideanMenu() {
712   // for(int i=2; i<lt; i++) landvisited[i] = true;
713 
714   cmode = sm::SIDE | sm::MAYDARK;
715   gamescreen(0);
716 
717   dialog::init(XLAT("experiment with geometry"));
718 
719   dialog::addSelItem(XLAT("geometry"), geometry_name(), 'd');
720   dialog::add_action([] { pushScreen(ge_select_tiling); pushScreen(ge_select_filter); });
721 
722   dialog::addSelItem(XLAT("basic tiling"), XLAT(ginf[geometry].tiling_name), 't');
723   dialog::add_action([] {
724     if(!current_filter || !current_filter->test()) set_default_filter();
725     pushScreen(ge_select_tiling);
726     });
727 
728   int ts = ginf[geometry].sides;
729   int tv = ginf[geometry].vertex;
730   int nom = (BITRUNCATED ? tv+ts : tv) * 4;
731   int denom = (2*ts + 2*tv - ts * tv);
732 
733   #if CAP_GP
734   if(GOLDBERG || INVERSE) {
735     ld area = PIU(cgi.gpdata->area);
736 
737     if(GOLDBERG || WARPED) {
738       nom = 2 * (2*tv + (S3-2) * ts * (area-1));
739       }
740     else if(UNRECTIFIED) {
741       if((gp::param.first + gp::param.second) % 2 == 0)
742         nom = ts * 2 * area;
743       else
744         nom = (2*tv + (S3-2) * ts * (area-1));
745       }
746     else if(UNTRUNCATED) {
747       if((gp::param.first - gp::param.second) % 3 == 0) {
748         nom = ts * 4 * area;
749         denom *= 3;
750         }
751       else {
752         nom = 2 * (2*tv + (S3-2) * ts * (area-1));
753         denom *= 3;
754         }
755       }
756     }
757   #endif
758 
759   int worldsize;
760 
761   int euler = 0;
762   if(euclid) euler = 0;
763   else if(sphere && nonorientable) euler = 1;
764   else if(sphere) euler = 2;
765   else if(!bounded) euler = -2;
766   else if(WDIM == 3) euler = 0;
767   else switch(geometry) {
768     case gFieldQuotient:
769       worldsize = isize(currentmap->allcells());
770       euler = 2 * worldsize * denom / nom;
771       break;
772 
773     case gMinimal:
774       euler = -1;
775       break;
776 
777     case gZebraQuotient:
778     case gBolza:
779       euler = -2;
780       break;
781 
782     case gKleinQuartic:
783     case gSchmutzM2:
784     case gBolza2:
785       euler = -4;
786       break;
787 
788     case gSchmutzM3:
789     case gBring:
790       euler = -6;
791       break;
792 
793     case gMacbeath:
794       euler = -12;
795       break;
796 
797     default:
798       worldsize = isize(currentmap->allcells());
799       println(hlog, "warning: Euler characteristics unknown, worldsize = ", worldsize);
800       euler = 2 * worldsize * denom / nom;
801       break;
802     }
803 
804   nom *= euler;
805   denom *= 2;
806 
807   if(hybri) nom *= hybrid::csteps, denom *= cgi.single_step;
808 
809   int g = gcd(nom, denom);
810   if(g) {
811     nom /= g;
812     denom /= g;
813     }
814 
815   if(euclid && bounded) {
816     worldsize = euc::eu.det;
817     if(BITRUNCATED) worldsize *= (a4 ? 2 : 3);
818     if(GOLDBERG) worldsize *= cgi.gpdata->area;
819     #if CAP_IRR
820     if(IRREGULAR) worldsize *= isize(irr::cells) / isize(irr::cells_of_heptagon);
821     #endif
822     }
823   else
824   worldsize = denom ? nom / denom : 0;
825 
826   if(euler < 0 && !bounded)
827     worldsize = -worldsize;
828 
829   string spf = its(ts);
830   if(0) ;
831   #if CAP_ARCM
832   else if(arcm::in()) {
833     spf = "";
834     for(int i: arcm::current.faces) {
835       if(spf != "") spf += ",";
836       spf += its(i);
837       }
838     if(BITRUNCATED) spf = "[" + spf + "]," + its(arcm::current.N * 2) + "," + its(arcm::current.N * 2);
839     if(DUAL) spf = its(arcm::current.N) + "^[" + spf + "]";
840     }
841   #endif
842   #if CAP_BT
843   else if(bt::in()) switch(geometry) {
844     case gBinaryTiling: spf = "6,[6,7],7"; break;
845     case gBinary4: spf = "5,5,5[,5]"; break;
846     case gTernary: spf = "6,6,6[,6]"; break;
847     default: spf = "?"; break;
848     }
849   #endif
850   else if(BITRUNCATED)
851     spf = spf + "," + its(S6) + "," + its(S6);
852   #if CAP_IRR
853   else if(IRREGULAR && irr::bitruncations_performed)
854     spf = "[4..8],6,6";
855   else if(IRREGULAR)
856     spf = "[4..8]^3";
857   #endif
858   #if CAP_GP
859   else if(UNRECTIFIED || UNTRUNCATED) {
860     if(UNRECTIFIED && (gp::param.first-gp::param.second) % 2 != 0)
861       spf = "(?)";
862     else if(UNTRUNCATED && (gp::param.first-gp::param.second) % 3 != 0)
863       spf = "(?)";
864     else {
865       string each = UNRECTIFIED ? "4" : "3";
866       int first = UNRECTIFIED ? 4 : 6;
867       int second = S7;
868       if(gp::param == gp::loc(1, 1))
869         first = second;
870       else if(second < first)
871         swap(first, second);
872       spf = each;
873       for(int z=1; z<first; z++) spf += "," + each;
874       if(first != second) {
875         spf += "[";
876         for(int z=first; z<second; z++) spf += "," + each;
877         spf += "]";
878         }
879       }
880     }
881   else if(GOLDBERG && S3 == 4 && gp::param == gp::loc(1, 1))
882     spf = spf + ",4," + spf + ",4";
883   else if(GOLDBERG && S3 == 4 && gp::param == gp::loc(2, 0))
884     spf = spf + ",4,4,4";
885   else if(GOLDBERG && S3 == 4)
886     spf = "[" + spf + ",4],4,4,4";
887   else if(WARPED && S3 == 3 && gp::param == gp::loc(1,1))
888     spf = spf + ",3,3";
889   else if(WARPED && S3 == 3)
890     spf = "[" + spf + ",6],3,3";
891   else if(GOLDBERG && S3 == 3)
892     spf = "[" + spf + ",6],6,6";
893   #endif
894   else {
895     string spf0 = spf;
896     for(int z=1; z<S3; z++) spf = spf + "," + spf0;
897     }
898 
899   string qstring = ginf[geometry].quotient_name;
900 
901   if(qstring == "none")
902     dialog::addBoolItem(XLAT("quotient space"), false, 'q');
903   else
904     dialog::addSelItem(XLAT("quotient space"), XLAT(qstring), 'q');
905 
906   dialog::add_action(select_quotient);
907 
908   #if CAP_ARCM
909   if(arcm::in()) {
910     dialog::addItem(XLAT("advanced parameters"), '4');
911     dialog::add_action_push(arcm::show);
912     }
913   #endif
914 
915   #if CAP_CRYSTAL
916   if(cryst) {
917     dialog::addItem(XLAT("advanced parameters"), '4');
918     dialog::add_action_push(crystal::show);
919     }
920   #endif
921 
922   if(fake::available()) {
923     dialog::addItem(XLAT("fake curvature"), '4');
924 
925     dialog::add_action([] {
926       if(fake::in()) fake::configure();
927       else dialog::cheat_if_confirmed(
928         fake::configure
929         );
930       });
931     }
932 
933   if(arb::in() && !arb::current.sliders.empty()) {
934     dialog::addItem(XLAT("tessellation sliders"), '4');
935     dialog::add_action_push(arb::set_sliders);
936     }
937 
938   #if CAP_IRR
939   if(hyperbolic && IRREGULAR) {
940     nom = isize(irr::cells);
941     // both Klein Quartic and Bolza2 are double the Zebra quotiennt
942     denom = -2;
943     if(!quotient) worldsize = nom / denom;
944     }
945   #endif
946 
947   #if MAXMDIM >= 4
948   if(cgflags & qULTRA) {
949     dialog::addBoolItem(XLAT("truncate ultra-vertices with mirrors"), reg3::ultra_mirror_on, 'Z');
950     dialog::add_action([] {
951       reg3::ultra_mirror_on = !reg3::ultra_mirror_on;
952       ray::reset_raycaster();
953       });
954     }
955   #endif
956 
957   if(prod) {
958     dialog::addSelItem(XLAT("Z-level height factor"), fts(vid.plevel_factor), 'Z');
959     dialog::add_action([] {
960       dialog::editNumber(vid.plevel_factor, 0, 2, 0.1, 0.7, XLAT("Z-level height factor"), "");
961       });
962     }
963   else if(hybri) {
964     dialog::addSelItem(XLAT("number of levels"), its(hybrid::csteps / cgi.single_step), 'L');
965     dialog::add_action(hybrid::configure_period);
966     }
967   else if(bt::in()) {
968     menuitem_binary_width('v');
969     add_edit_wall_quality('W');
970     }
971   else if(nil) {
972     menuitem_nilwidth('v');
973     }
974   else if((WDIM == 3 || kite::in() || arb::in()) && !reg3::in()) dialog::addBreak(100);
975   else
976     menuitem_change_variation('v');
977 
978   if(in_s2xe()) {
979     dialog::addSelItem(XLAT("precision of S2xE rings"), its(s2xe::qrings), '5');
980     dialog::add_action([] {
981       dialog::editNumber(s2xe::qrings, 1, 256, 4, 32, XLAT("precision of S2xE rings"),
982         XLAT(
983           "In S2xE, objects at spherical distances which are multiples of π will look like "
984           "rings, and objects close to these will look like crescents. "
985           "This setting controls the quality of rendering these rings and crescents.")
986         );
987       dialog::bound_low(1);
988       dialog::bound_up(256);
989       });
990     }
991 
992   #if MAXMDIM >= 4
993   if(hybri) {
994     auto r = rots::underlying_scale;
995     dialog::addSelItem(XLAT("view the underlying geometry"), r > 0 ? fts(r)+"x" : ONOFF(false), '6');
996     dialog::add_action([] {
997       dialog::editNumber(rots::underlying_scale, 0, 1, 0.05, 0.25, XLAT("view the underlying geometry"),
998         geometry == gRotSpace ?
999           XLAT("The space you are currently in is the space of rotations of the underlying hyperbolic or spherical geometry. ")
1000         : XLAT("You are currently in a product space.") +
1001         XLAT(
1002           "This option lets you see the underlying space. Lands and some walls (e.g. in the Graveyard) are based on "
1003           "the respective features in the underlying world, but details such as monsters or items are ignored."
1004           )
1005         );
1006       dialog::bound_low(0);
1007       dialog::bound_up(1);
1008       dialog::extra_options = [] () { rots::draw_underlying(true); };
1009       });
1010     }
1011   #endif
1012 
1013   if(stretch::applicable()) {
1014     dialog::addSelItem(XLAT("stretched geometry"), fts(stretch::factor), 'S');
1015     dialog::add_action(edit_stretch);
1016     }
1017 
1018   dialog::addBreak(100);
1019   menuitem_land_structure('l');
1020 
1021   if(specialland == laMinefield && bounded) {
1022     dialog::addSelItem(XLAT("number of mines"), its(bounded_mine_quantity), 'm');
1023     dialog::add_action([] {
1024       dialog::editNumber(bounded_mine_quantity, 0, bounded_mine_max, 1, (bounded_mine_max+5)/10,
1025         XLAT("number of mines"), "");
1026       dialog::reaction = [] {
1027         if(bounded_mine_quantity < 0) bounded_mine_quantity = 0;
1028         if(bounded_mine_quantity > bounded_mine_max) bounded_mine_quantity = bounded_mine_max;
1029         };
1030       dialog::reaction_final = [] {
1031         bounded_mine_percentage = bounded_mine_quantity * 1. / bounded_mine_max;
1032         stop_game();
1033         start_game();
1034         };
1035       });
1036     }
1037 
1038   if(geometry_has_alt_mine_rule()) {
1039     dialog::addSelItem(XLAT("adjacency rule"), mine_adjacency_rule ? XLAT("vertex") : WDIM == 3 ? XLAT("face") : XLAT("edge"), 'M');
1040     dialog::add_action([] {
1041       stop_game();
1042       mine_adjacency_rule = !mine_adjacency_rule;
1043       start_game();
1044       addMessage(XLAT("Note: adjacency rule affects environmental effects, but not movement."));
1045       });
1046     }
1047 
1048   dialog::addBoolItem(XLAT("pattern"), specialland == laCanvas, 'p');
1049   if(specialland == laCanvas) dialog::lastItem().value = patterns::whichCanvas;
1050   dialog::add_action_push(patterns::showPrePattern);
1051   validity_info();
1052   if(WDIM == 3) {
1053     dialog::addItem(XLAT("3D configuration"), '9');
1054     dialog::add_action_push(show3D);
1055     }
1056   menuitem_projection('1');
1057   if(nonisotropic && !sl2)
1058     dialog::addBoolItem_action(XLAT("geodesic movement in Sol/Nil"), nisot::geodesic_movement, 'G');
1059   #if CAP_CRYSTAL && MAXMDIM >= 4
1060   crystal::add_crystal_transform('x');
1061   #endif
1062 
1063   dialog::addBreak(50);
1064 
1065   #if CAP_SHOT
1066   dialog::addItem(XLAT("take screenshot"), 's');
1067   dialog::add_action_push(shot::menu);
1068   #endif
1069 
1070   dialog::addHelp();
1071   dialog::addBack();
1072 
1073   dialog::addBreak(150);
1074 
1075   dialog::addTitle(XLAT("info about: %1", full_geometry_name()), 0xFFFFFF, 150);
1076 
1077   if(WDIM == 2 && !arb::in() && !kite::in()) dialog::addSelItem(XLAT("faces per vertex"), spf, 0);
1078 
1079   if(arb::in() && arb::current.comment != "") {
1080     dialog::addBreak(100);
1081     dialog::addHelp(arb::current.comment);
1082     }
1083 
1084   dialog::addSelItem(XLAT("size of the world"),
1085     #if CAP_BT
1086     bt::in() ? fts(8 * M_PI * sqrt(2) * log(2) / pow(vid.binary_width, WDIM-1), 4) + " exp(∞)" :
1087     #endif
1088     #if CAP_ARCM
1089     arcm::in() && (WDIM == 2) ? arcm::current.world_size() :
1090     (arcm::in() && sphere) ? its(isize(currentmap->allcells())) :
1091     #endif
1092     #if CAP_CRYSTAL
1093     cryst ? "∞^" + its(ts/2) :
1094     #endif
1095     WDIM == 3 && bounded ? its(isize(currentmap->allcells())) :
1096     WDIM == 3 && euclid ? "∞" :
1097     worldsize < 0 ? (nom%denom ? its(nom)+"/"+its(denom) : its(-worldsize)) + " exp(∞)":
1098     (euclid && quotient && !bounded) ? "∞" :
1099     worldsize == 0 ? "∞²" :
1100     its(worldsize),
1101     '3');
1102 
1103   if(WDIM == 2 || reg3::in_rule()) dialog::add_action([] {
1104     if(!viewdists) { enable_viewdists(); pushScreen(viewdist_configure_dialog); }
1105     else if(viewdists) viewdists = false;
1106     });
1107 
1108   if(bounded) {
1109     if(WDIM == 3) euler = 0;
1110     dialog::addSelItem(XLAT("Euler characteristics"), its(euler), 0);
1111     if(WDIM == 3) ;
1112     else if(nonorientable)
1113       dialog::addSelItem(XLAT("demigenus"), its(2-euler), 0);
1114     else
1115       dialog::addSelItem(XLAT("genus"), its((2-euler)/2), 0);
1116     }
1117   else dialog::addBreak(200);
1118 
1119   dialog::display();
1120   }
1121 
runGeometryExperiments()1122 EX void runGeometryExperiments() {
1123   if(!geometry && specialland == laIce)
1124     specialland = getLandForList(cwt.at);
1125   pushScreen(showEuclideanMenu);
1126   }
1127 
1128 #if CAP_COMMANDLINE
1129 
readGeo(const string & ss)1130 EX eGeometry readGeo(const string& ss) {
1131   for(int i=0; i<isize(ginf); i++) if(ginf[i].shortname == ss) return eGeometry(i);
1132   bool numeric = true;
1133   for(char c: ss) if(c < '0' || c > '9') numeric = false;
1134   if(numeric) return eGeometry(atoi(ss.c_str()));
1135   for(int i=0; i<isize(ginf); i++) if(appears(ginf[i].menu_displayed_name, ss)) {
1136     return eGeometry(i);
1137     break;
1138     }
1139   return gNormal;
1140   }
1141 
field_quotient_3d(int p,unsigned hash)1142 EX void field_quotient_3d(int p, unsigned hash) {
1143   check_cgi();
1144   cgi.require_basics();
1145   stop_game_and_switch_mode(rg::nothing);
1146   fieldpattern::field_from_current();
1147   set_geometry(gFieldQuotient);
1148   for(;; p++) {
1149     println(hlog, "trying p = ", p);
1150     currfp.Prime = p; currfp.force_hash = hash; if(!currfp.solve()) break;
1151     }
1152   println(hlog, "set prime = ", currfp.Prime);
1153   }
1154 
field_quotient_2d(int group,int id,int triplet)1155 EX void field_quotient_2d(int group, int id, int triplet) {
1156   using namespace fieldpattern;
1157   current_extra = group;
1158 
1159   auto& gxcur = fgeomextras[current_extra];
1160   while(id >= isize(gxcur.primes)) nextPrime(gxcur);
1161 
1162   fgeomextras[current_extra].current_prime_id = id;
1163   if(triplet != -1)
1164     triplet_id = triplet;
1165   enableFieldChange();
1166   set_geometry(gFieldQuotient);
1167   }
1168 
read_geom_args()1169 int read_geom_args() {
1170   using namespace arg;
1171   if(0) ;
1172   #if CAP_FIELD
1173   else if(argis("-qpar")) {
1174     int p;
1175     shift(); sscanf(argcs(), "%d,%d,%d",
1176       &p, &quotientspace::rvadd, &quotientspace::rvdir
1177       );
1178     autocheat = true;
1179     currfp.init(p);
1180     }
1181   else if(argis("-qpar2")) {
1182     stop_game_and_switch_mode(rg::nothing);
1183     int a, b;
1184     shift(); sscanf(argcs(), "%d,%d", &a, &b);
1185     field_quotient_2d(a, b, -1);
1186     }
1187   else if(argis("-triplet")) {
1188     stop_game();
1189     shift(); fieldpattern::triplet_id = argi();
1190     fieldpattern::enableFieldChange();
1191     }
1192   else if(argis("-to-fq")) {
1193     int p = 2;
1194     shift();
1195     if(args() == "p") {
1196       shift(); p = argi();
1197       shift();
1198       }
1199     unsigned hash = arghex();
1200     field_quotient_3d(p, hash);
1201     }
1202   else if(argis("-cs")) {
1203     shift(); cheat();
1204     fieldpattern::matrix M = currfp.strtomatrix(args());
1205     fieldpattern::subpathid = currfp.matcode[M];
1206     fieldpattern::subpathorder = currfp.order(M);
1207     }
1208   else if(argis("-fwrite")) {
1209     shstream hs;
1210     hwrite_fpattern(hs, currfp);
1211     println(hlog, "current fieldpattern hash: ", currfp.hashv);
1212     println(hlog, "current fieldpattern: ", as_cstring(hs.s));
1213     }
1214   else if(argis("-csp")) {
1215     cheat();
1216     currfp.findsubpath();
1217     }
1218   #endif
1219   else if(argis("-mineadj")) {
1220     shift(); mine_adjacency_rule = argi();
1221     }
1222   TOGGLE('7', PURE, set_variation(PURE ? eVariation::bitruncated : eVariation::pure))
1223   else if(argis("-geo")) {
1224     PHASEFROM(2);
1225     shift();
1226     set_geometry(readGeo(args()));
1227     }
1228   #if CAP_GP
1229   else if(argis("-gp")) {
1230     PHASEFROM(2);
1231     shift(); gp::param.first = argi();
1232     shift(); gp::param.second = argi();
1233     set_variation(eVariation::goldberg);
1234     }
1235   else if(argis("-unrectified")) {
1236     PHASEFROM(2);
1237     set_variation(eVariation::unrectified);
1238     }
1239   else if(argis("-untruncated")) {
1240     PHASEFROM(2);
1241     set_variation(eVariation::untruncated);
1242     }
1243   else if(argis("-warped")) {
1244     PHASEFROM(2);
1245     set_variation(eVariation::warped);
1246     }
1247   else if(argis("-subcubes")) {
1248     PHASEFROM(2);
1249     stop_game();
1250     set_variation(eVariation::subcubes);
1251     shift(); reg3::subcube_count = argi();
1252     }
1253   else if(argis("-dual-subcubes")) {
1254     PHASEFROM(2);
1255     stop_game();
1256     set_variation(eVariation::dual_subcubes);
1257     shift(); reg3::subcube_count = argi();
1258     }
1259   else if(argis("-bch-subcubes")) {
1260     PHASEFROM(2);
1261     stop_game();
1262     set_variation(eVariation::bch);
1263     shift(); reg3::subcube_count = argi();
1264     }
1265   else if(argis("-bch-oct")) {
1266     PHASEFROM(2);
1267     stop_game();
1268     set_variation(eVariation::bch_oct);
1269     shift(); reg3::subcube_count = argi();
1270     }
1271   else if(argis("-coxeter")) {
1272     PHASEFROM(2);
1273     stop_game();
1274     set_variation(eVariation::coxeter);
1275     shift(); reg3::coxeter_param = argi();
1276     }
1277   #endif
1278   #if CAP_FIELD
1279   else if(argis("-fi")) {
1280     fieldpattern::info();
1281     exit(0);
1282     }
1283   else if(argis("-fi-at")) {
1284     geometry = gNormal;
1285     shift(); dynamicval<int> s7(S7, argi());
1286     shift(); dynamicval<int> s3(S3, argi());
1287     fieldpattern::info();
1288     exit(0);
1289     }
1290   else if(argis("-fi-geo")) {
1291     fieldpattern::info();
1292     exit(0);
1293     }
1294   else if(argis("-qs")) {
1295     cheat();
1296     shift(); currfp.qpaths.push_back(args());
1297     }
1298   #if MAXMDIM >= 4
1299   else if(argis("-truncate-ultra")) {
1300     shift(); reg3::ultra_mirror_on = argi();
1301     }
1302   #endif
1303   else if(argis("-d:quotient"))
1304     launch_dialog(showQuotientConfig);
1305   else if(argis("-uqf"))
1306     fieldpattern::use_quotient_fp = true;
1307   #endif
1308   else if(argis("-d:geom"))
1309     launch_dialog(showEuclideanMenu);
1310   else return 1;
1311   return 0;
1312   }
1313 
1314 auto ah_geom = addHook(hooks_args, 0, read_geom_args);
1315 #endif
1316 
1317 }
1318