makhidkarun / traveller_pyroute

Traveller trade route generator
MIT License
14 stars 5 forks source link

Investigate straight star distances #51

Closed CyberiaResurrection closed 1 year ago

CyberiaResurrection commented 1 year ago

Companion PR to #50 .

Probably need to be merged after #49

Straight distances so far after second round of substantive fixen (changes from previous are asterisked): Star A Star B Straight Distance (pc) Manual Distance (pc)
Core 0104 Core 2209 21 21
Lishun 0104 Lishun 2209 21 21
Core 0105 Core 0525 22* 22
Vland 0105 Vland 0525 22* 22
Core 0205 Core 1315 15* 15
Dagudashaag 0205 Dagudashaag 1315 15* 15
Core 0204 Core 1215 16* 16
Zarushagar 0204 Zarushagar 1215 16* 16
Core 0105 Core 1216 17* 17
Massilia 0105 Massilia 1216 17* 17
Core 0204 Core 2208 20 20
Delphi 0204 Delphi 2208 20 20
Core 0204 Core 0725 23* 23
Fornast 0204 Fornast 0725 23* 23
Core 0105 Core 2210 21 21
Antares 0105 Antares 2210 21 21
Core 0910 Core 1407 5 5
Core 0910 Core 1507 6 6
Core 0910 Core 1412 5* 5
Core 0910 Core 1513 6* 6
Core 0910 Core 0914 5 5
Core 0910 Core 0915 6 6
Core 0910 Core 1410 5 5
Core 0910 Core 1510 6 6
Extending that to inter-sector distances (Zhdant sector having sector co-ords -7, 2) (edited to add dx/dy data and category, fix transpositions and new data): Star A Star B Straight Distance (pc) Hex Distance (pc) Target Sector Co-ords raw dx/dy dx/dy category
Zhdant 0810 Tsadra 0910 55* 55 -8, 1 -31/-48 31/47 dy > dx
Zhdant 0810 Chit Botshti 0911 112 112 -5, 4 +65/+94 65/95 dy > dx
Zhdant 0911 Chiep Zhez 0810 112 112 -9, 0 -65/-95 65/95 dy > dx
Zhdant 2530 Pliabriebl 0811 81 81 -9, 2 -81/+118 81/117 dy > dx
Zhdant 0811 Brieplanz 2430 48 48 -9, 3 -48/+90 48/90 dy > dx
Zhdant 2511 Eiaplial 2431 37 37 -8, 2 -33/-8 33/9 dx//2 > dy
Zhdant 0830 Tsadra Davr 2511 47 47 -9, 1 -47/+6 47/7 dx//2 > dy
Zhdant 2411 Itvikiastaf 2531 65 65 -5, 3 +65/-24 65/23 dx//2 > dy
Zhdant 0830 Ziafrplians 2410 80 80 -5, 2 +80/-40 80/40 dx//2 == dy
Zhdant 2511 Steblenzh 2510 97 97 -6, 0 +32/+130 32/130 dy//2 > dx
Zhdant 0931 Chtedrdia 2430 88 88 -7, 0 +15/+146 15/145 dy//2 > dx
Zhdant 0931 Afachtiabr 0810 76 76 -6, 1 +31/+90 31/89 dy//2 > dx
Zhdant 2411 Zdiedeiant 0930 47 47 -7, 3 -16/-62 16/62 dy//2 > dx
Zhdant 0810 Shiants 2510 87 87 -8, 0 -15/-144 15/143 dy//2 > dx
Zhdant 0830 Driasera 0931 81 81 -7, 4 +1/-162 1/161 dy//2 > dx
Zhdant 2531 Iakr 0830 64* 64 -5, 1 +47/-126 47/127 dy//2 > dx
Zhdant 2430 Yiklerzdanzh 2511 60 60 -7, 1 +1/+118 1/119 dy//2 > dx
Zhdant 2510 Zhdiakitlatl 2410 113* 113 -5, 0 +63/-224 63/225 dy//2 > dx
Zhdant 2411 Viajlefliez 0931 101* 101 -9, 4 -79/+200 79/201 dy//2 > dx
Zhdant 2530 Bleblqlansh 2411 115 115 -8, 4 -33/+230 33/229 dy//2 > dx
Zhdant 2430 Dalchie Jdatl 2431 97 97 -6, 4 +32/-194 32/194 dy//2 > dx
Zhdant 0810 Tienspevnekr 0930 36 36 -6, 2 +33/-72 33/71 dy//2 > dx
Zhdant 0831 Sidiadi 0831 56 56 -8, 3 -32/+112 32/112 dy//2 > dx
Zhdant 0911 Zdiedeiant 2430 68* 68 -7, 3 +15/-134 15/135 dy//2 > dx
Zhdant 2530 Stiatlchepr 0911 29 29 -6, 3 +16/-58 16/58 dy//2 > dx
CyberiaResurrection commented 1 year ago

@tjoneslo , that the scale of checking you were thinking of?

Current star.distance code that generated those straight distances (as at this writing):


    def distance(self, star, diagnostic=False):
        # If star.x and self.x are both even or both odd (implying dx must be even), they have no net effect on dy's value
        # If both even, y1 and y2 each get 1 added, which drops out in subtraction
        # If both odd, y1 and y2 each get nothing added.
        y1 = self.y * 2
        y2 = star.y * 2
        dx = abs(star.x - self.x)
        raw_dy = y2 - y1
        raw_dx = star.x - self.x

        # Only look at the following if dx is odd, implying exactly one of self.x and star.x is even
        # If star.x is even and self.x is odd, raw value of dy is += 1 (y2 gets increased, y1 stays same)
        # If self.x is even and star.x is odd, raw value of dy is -= 1 (y1 gets increased, y2 stays same)
        if dx % 2:
            # If self.x is even, bump y1 up by 1
            if not self.x % 2:
                y1 += 1
            # If self.x is odd, then star.x _MUST_ be even, bump y2 up by 1
            else:
                y2 += 1

        # The hexagons we're using have 3 axes of symmetry
        # - The 12 o'clock-6 o'clock line
        # - The 10 o'clock-4 o'clock line
        # - The 8 o'clock-2'o'clock line
        # Let's tackle those special cases _first_, to try to simplify the residual cases
        dy = abs(y2 - y1)

        # 12 o'clock-6 o'clock line
        if 0 == dx:
            return dy//2 if not diagnostic else (dy//2, raw_dx, raw_dy, dx, dy)
        # 10 o'clock-4 o'clock line
        if dx * 2 == dy:
            return dx if not diagnostic else (dx, raw_dx, raw_dy, dx, dy)
        # 8 o'clock-2 o'clock line
        if 0 == raw_dy:
            return dx if not diagnostic else (dx, raw_dx, raw_dy, dx, dy)

        # connecting segment is between 12 o'clock and 2 o'clock exclusive,
        # xor between 6 o'clock and 8 o'clock exclusive
        if (0 < raw_dx) == (0 < raw_dy):
            if (dx // 2) > dy:
                return dx + (dy // 2) if not diagnostic else (dx + (dy // 2), raw_dx, raw_dy, dx, dy)
            if (dy // 2) > dx and abs(raw_dy) == dy:
                return dx + (dy // 2) if not diagnostic else (dx + (dy // 2), raw_dx, raw_dy, dx, dy)
            if dy > dx and not (dy // 2) > dx:
                return dx + (dy // 2) if not diagnostic else (dx + (dy // 2), raw_dx, raw_dy, dx, dy)
            # if we haven't found a definitive category yet, drop through to the remaining segment
            pass
        # connecting segment is in the bowtie
        # between 8 o'clock and 10 o'clock exclusive, xor between 2 o'clock and 4 o'clock exclusive
        if dx > (dy // 2):
            return dx if not diagnostic else (dx, raw_dx, raw_dy, dx, dy)
        # by exhaustion, connecting segment is between 10 o'clock and 12 o'clock exclusive,
        # xor 4 o'clock and 6 o'clock exclusive
        if (dy // 2) > dx:
            return ((dy + 1) // 2) if not diagnostic else (((dy + 1) // 2), raw_dx, raw_dy, dx, dy)
        return dx + (dy // 2) if not diagnostic else (dx + (dy // 2), raw_dx, raw_dy, dx, dy)
CyberiaResurrection commented 1 year ago

Breaking that down by category (counting "bad" as more than 1 parsec difference between straight and hex distances) updating totals, and adding in what I've learned:

dy // 2 > dx category: Raw dx and raw dy have opposite signs: 9 good, 0 bad, 9 total Raw dx and raw dy have same signs, dx even: 2 good, 0 bad, 2 total Raw dx and raw dy have same signs, dx odd: 2 good, 3 bad, 5 total

dy > dx category: Raw dx and raw dy have opposite signs: 2 good, 0 bad, 2 total Raw dx and raw dy have same signs, dx even: 0 good, 0 bad, 0 total Raw dx and raw dy have same signs, dx odd: 3 good, 0 bad, 3 total

dx//2 >= dy category: Raw dx and raw dy have opposite signs: 3 good, 0 bad, 3 total Raw dx and raw dy have same signs, dx even: 0 good, 0 bad, 0 total Raw dx and raw dy have same signs, dx odd: 1 good, 0 bad, 1 total

Over all 25 inter-sector straight distance comparisons: Raw dx and raw dy have opposite signs: 14 good, 0 bad, 14 total Raw dx and raw dy have same signs, dx even: 2 good, 0 bad, 2 total Raw dx and raw dy have same signs, dx odd: 6 good, 3 bad, 9 total

CyberiaResurrection commented 1 year ago

@tjoneslo , zooming in further on the dy //2 > dx case, with same raw_dx and raw_dy signs, without confusing myself in the previous comment:

Abs(raw_dy) > dy: 0 good, 3 bad, 3 total Abs(raw_dy) == dy: 2 good, 0 bad, 2 total Abs(raw_dy) < dy: 2 good, 0 bad, 2 total

CyberiaResurrection commented 1 year ago

@tjoneslo, more progress.

Good rows now have straight distance equal to hex distance. Bad rows don't (in practice, differing by +1 or -1 only). I've combined both blocks of distances for this analysis.

42 good rows, 7 bad rows, 49 total

0 = dx: 2 good, 0 bad, 2 total same dy and dx signs: 9 good, 2 bad, 11 total different dy and dx signs: 31 good, 5 bad, 36 total

abs(raw_dy) > dy: 12 good, 1 bad, 13 total abs(raw_dy) == dy: 18 good, 0 bad, 18 total abs(raw_dy) < dy: 12 good, 6 bad, 18 total

tjoneslo commented 1 year ago

https://www.redblobgames.com/grids/hexagons/ - This is the original source of the distance calculations. One concern would be I copied the formula's wrong. So that would be the first thing I would double check. Second would be the conversion of the sector/hex id to the coordinates used by the distance calculations. Does the conversion for the coreward row of Spinward Marches end up being 1 hex to the rimward row of Gvurrdon sector?

CyberiaResurrection commented 1 year ago

@tjoneslo - good point. Is what Amit Patel called "col" equal to what you've called "col", or is that x? (likewise for row and y).

What were you aiming to implement? distance over doubled-for-the-occasion offset co-ords?

To make sure I'm grokking you, you want me to:

tjoneslo commented 1 year ago

As I remember it, it should be:

CyberiaResurrection commented 1 year ago

or should I just do what redblob recommend and thin out straight distance to a wrapper around axial?

CyberiaResurrection commented 1 year ago

Sod it, straight distance is now a wrapper. Works out a hell of a lot simpler.

CyberiaResurrection commented 1 year ago

@tjoneslo , while I was waiting, I tried a full-charted-space run, which blew up with something like this:


2023-08-30 14:20:24,905 - INFO - Total number of worlds: 51190
Traceback (most recent call last):
  File "/home/alex/gitstuf/traveller_pyroute/PyRoute/route.py", line 147, in <module>
    process()
  File "/home/alex/gitstuf/traveller_pyroute/PyRoute/route.py", line 89, in process
    galaxy.read_sectors(sectors_list, args.pop_code, args.ru_calc)
  File "/home/alex/gitstuf/traveller_pyroute/PyRoute/Galaxy.py", line 331, in read_sectors
    self.set_positions()
  File "/home/alex/gitstuf/traveller_pyroute/PyRoute/Galaxy.py", line 352, in set_positions
    assert map_len == shadow_len, "Mismatch between shadow stars and stars mapping, " + str(shadow_len) + " and " + str(map_len)
AssertionError: Mismatch between shadow stars and stars mapping, 51190 and 52140

"Dedupe input file list" and "Dedupe sectors on loading" are simple efforts to robustify the parser against such straight-up duplication. "Dedupe input file list" guards against including the same sector file twice. "Dedupe sectors on loading" guards against situations like Zhodane/Zhdant as separate files referencing the same sector.

tjoneslo commented 1 year ago

This looks great