Skip to content

Commit 5d5aecb

Browse files
committed
fix TLA orientations in metarules
This adds a bandaid to prevent that mirrored orientations of orth/diag TLA tiles get added to the tile orientation cache. This avoids some ambiguity between the two flavors of turning lanes (`0x5...` vs `0x7...`) in the metarule-generated RUL2 code. Additionally, this adjust the preprocessor to duplicate rules involving TLA by their upside-down variant (`R2F1`). This is necessary, as the two rules might resolve to different IDs (`0x5...` vs `0x7...`).
1 parent e040955 commit 5d5aecb

4 files changed

Lines changed: 54 additions & 24 deletions

File tree

src/main/scala/module/Main.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ abstract class AbstractMain {
3030
}
3131
}
3232

33+
private lazy val shouldIgnoreMirroredOrientations: Set[Int] = MirrorVariants.ignoreMirroredOrientations(resolve)
34+
3335
def main(args: Array[String]): Unit = start()
3436

3537
/** Creates a generator with a new context, runs its start method and outputs the resulting RUL2 code to file. */
@@ -54,6 +56,14 @@ abstract class AbstractMain {
5456
printer.println(rule.toRul2String)
5557
}
5658
}
59+
// finally remove accumulated orientations that we want to ignore (like accidentally mirrored TLAs)
60+
tileOrientationCache.accum.mapValuesInPlace { (id, repr) =>
61+
if (shouldIgnoreMirroredOrientations(id)) repr.filterNot(_.flipped)
62+
else repr
63+
}
64+
tileOrientationCache.accum.filterInPlace { (id, repr) => // remove from accum if equal to cache (so that regenerateTileOrientationCache stabilizes eventually)
65+
!tileOrientationCache.cache.get(id).contains(repr)
66+
}
5767
}
5868
}
5969
}

src/main/scala/module/MirrorVariants.scala

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,43 @@ object MirrorVariants {
5454
case _ => tile
5555
}
5656

57-
private val tlaPreprocessor: Rule[SymTile] => Iterator[Rule[SymTile]] = rule => {
58-
if (!rule.exists(containsTlaFlags)) {
59-
Iterator(rule)
60-
} else if (rule.forall(shouldProjectTlaLeftOnly)) {
61-
Iterator(rule.map(projectTlaLeft))
62-
} else {
63-
Iterator(rule.map(projectTlaLeft), rule.map(projectTlaRight))
64-
}
57+
val preprocessor: Rule[SymTile] => Iterator[Rule[SymTile]] = rule => {
58+
val hasMirrorVariant = rule.exists(tile => mirrorVariants.contains(tile))
59+
for {
60+
rule <- if (hasMirrorVariant) Iterator( // yield the two projected rules
61+
rule.map(tile => mirrorVariants.get(tile).map(_._1).getOrElse(tile)),
62+
rule.map(tile => mirrorVariants.get(tile).map(_._2).getOrElse(tile)),
63+
)
64+
else Iterator(rule)
65+
hasTlaFlags = rule.exists(containsTlaFlags)
66+
rule <- if (!hasTlaFlags) Iterator(rule)
67+
else if (rule.forall(shouldProjectTlaLeftOnly)) Iterator(rule.map(projectTlaLeft))
68+
else Iterator(rule.map(projectTlaLeft), rule.map(projectTlaRight))
69+
// We duplicate the rule by its R2F1 variant if there are TLAs or mirror
70+
// variants involved, as the left/right distinction can make them resolve
71+
// to different IDs (and the generators do not necessarily account for
72+
// this). For example, this is needed for Tla5/Avenue D×D.
73+
rule <- if (hasMirrorVariant || hasTlaFlags) Iterator(rule, rule.map(_ * R2F1)).distinct
74+
else Iterator(rule)
75+
} yield rule
6576
}
6677

67-
val preprocessor: Rule[SymTile] => Iterator[Rule[SymTile]] = rule => {
68-
if (!rule.exists(tile => mirrorVariants.contains(tile))) {
69-
Iterator(rule)
70-
} else {
71-
Iterator( // yield the two projected rules
72-
rule.map(tile => mirrorVariants.get(tile).map(_._1).getOrElse(tile)),
73-
rule.map(tile => mirrorVariants.get(tile).map(_._2).getOrElse(tile)))
78+
/* For plain orthogonal and diagonal tiles of TLA networks, we ignore any
79+
* remapping defined in `tileOrientationCache` that leads to mirroring, as
80+
* that would amplify the mirroring problems due to the presence of turning
81+
* lanes. Not a perfect solution, as occasional mirroring problems will remain.
82+
*/
83+
def ignoreMirroredOrientations(resolve: IdResolver): Set[Int] = {
84+
val ids = Set.newBuilder[Int]
85+
for {
86+
n <- Seq(Tla3, Tla5, Tla7m, Road, Onewayroad) // TODO consider adding all the networks
87+
dir <- Seq(NS, ES, SE) // these are arbitrary orth/diag directions to allow us find the IDs
88+
tile <- Seq(NetworkProperties.projectTlaLeft(n~dir), NetworkProperties.projectTlaRight(n~dir))
89+
idTile <- resolve.lift(tile)
90+
} {
91+
ids += idTile.id
7492
}
75-
}.flatMap(tlaPreprocessor)
93+
ids.result()
94+
}
95+
7696
}

src/test/scala/meta/RuleTransducerSpec.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class RuleTransducerSpec extends AnyWordSpec with Matchers {
2222

2323
context.preprocess( Tla3~WE & Road~NS | (Road ~> Tla3)~WE ).toSeq should have size (1)
2424
val diag = context.preprocess( Tla3~WE & Road~WS | (Road ~> Tla3)~WE ).toSeq
25-
diag should have size (2)
25+
diag should have size (4)
2626
for (r <- diag) {
2727
createRules(r.map(_.toIdSymTile(resolver)), context.tileOrientationCache.cache, context.tileOrientationCache.accum).toSeq should have size (2)
2828
}

src/test/scala/module/MirrorVariantsSpec.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,29 +56,29 @@ class MirrorVariantsSpec extends AnyWordSpec with Matchers {
5656

5757
"produce expected results for TLA network crossings" in {
5858
implicit val context = RuleTransducer.Context(resolve, preprocess = MirrorVariants.preprocessor)
59-
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Rail~ES).toSeq shouldBe Seq(
59+
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Rail~ES).toSeq should contain theSameElementsAs Seq(
6060
Rule(0x51000000,1,0, 0x03010200,1,0, 0x51000000,1,0, 0x51005500,3,0),
6161
Rule(0x51000000,3,0, 0x03010200,1,0, 0x51000000,3,0, 0x51005500,3,0),
6262
Rule(0x51000000,3,0, 0x03020500,3,1, 0x51000000,3,0, 0x51005500,1,1),
6363
Rule(0x51000000,1,0, 0x03020500,3,1, 0x51000000,1,0, 0x51005500,1,1))
64-
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Rail~ES).toSeq shouldBe Seq(
64+
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Rail~ES).toSeq should contain theSameElementsAs Seq(
6565
Rule(0x51100000,3,0, 0x03010200,1,0, 0x51100000,3,0, 0x51105500,3,0),
6666
Rule(0x51100000,1,0, 0x03020500,3,1, 0x51100000,1,0, 0x51105500,1,1))
67-
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Road~ES).toSeq shouldBe Seq(
67+
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Road~ES).toSeq should contain theSameElementsAs Seq(
6868
Rule(0x51100000,3,0, 0x00003900,1,0, 0x51100000,3,0, 0x51105100,3,0),
6969
Rule(0x51100000,1,0, 0x00003900,3,1, 0x51100000,1,0, 0x71105100,1,1)) // 0x71... variant
70-
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Lightrail~ES).toSeq shouldBe Seq(
70+
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Lightrail~ES).toSeq should contain theSameElementsAs Seq(
7171
Rule(0x51000000,1,0, 0x08DD1600,1,1, 0x51000000,1,0, 0x51005600,3,0),
7272
Rule(0x51000000,3,0, 0x08DD1600,1,1, 0x51000000,3,0, 0x51005600,3,0),
7373
Rule(0x51000000,3,0, 0x08DD1600,3,0, 0x51000000,3,0, 0x51005600,1,1),
7474
Rule(0x51000000,1,0, 0x08DD1600,3,0, 0x51000000,1,0, 0x51005600,1,1))
75-
RuleTransducer(Tla5~EW | (Avenue~>Tla5)~EW & Avenue~NS).toSet shouldBe Set(
75+
RuleTransducer(Tla5~EW | (Avenue~>Tla5)~EW & Avenue~NS).toSeq should contain theSameElementsAs Seq(
7676
Rule(0x51100000,3,0, 0x04009000,0,0, 0x51100000,3,0, 0x71101300,2,1),
7777
Rule(0x51100000,1,0, 0x04009000,1,0, 0x51100000,1,0, 0x51101300,0,0))
78-
RuleTransducer(Tla5~EW & Avenue~NS | (Avenue~>Tla5)~EW & Avenue~SN).toSet shouldBe Set(
78+
RuleTransducer(Tla5~EW & Avenue~NS | (Avenue~>Tla5)~EW & Avenue~SN).toSeq should contain theSameElementsAs Seq(
7979
Rule(0x71101300,2,1, 0x04009000,3,0, 0x71101300,2,1, 0x51101300,2,0),
8080
Rule(0x51101300,0,0, 0x04009000,2,0, 0x51101300,0,0, 0x71101300,0,1))
81-
RuleTransducer(Tla5~EW & Avenue~SN | (Avenue~>Tla5)~EW).toSet shouldBe Set(
81+
RuleTransducer(Tla5~EW & Avenue~SN | (Avenue~>Tla5)~EW).toSeq should contain theSameElementsAs Seq(
8282
Rule(0x51101300,2,0, 0x04006100,3,0, 0x51101300,2,0, 0x51100000,3,0),
8383
Rule(0x71101300,0,1, 0x04006100,1,0, 0x71101300,0,1, 0x51100000,1,0))
8484
}

0 commit comments

Comments
 (0)