Arjen Wiersma

A blog on Emacs, self-hosting, Clojure and other nerdy things

So, today I have some news. I will be resigning as Ambassador for Hack The Box after our in-person meetup in June (2024). This means that I will be stepping down from organizing the monthly virtual and quarterly in-person Hack The Box meetups. Let me explain how I got to this decision.

The beginning {#the-beginning}

So, in 2019, I started out building a cyber security curriculum for NOVI Hogeschool. I had the ability to greenfield the courses and create something that is of value to students. In this curriculum I started using Hack The Box for exercises and training next to the regular classwork.

As more and more of my students went through the program I thought it would be cool to get together with the community in a meetup. I contacted Hack The Box and after a little work, the Hack The Box NL meetup was born. This was end of February 2020, and we were aiming for our first meetup in April of 2020. As you all know, the world changed in those days and our plans went into the dumpster. Instead we started hosting a monthly online meetup.

Only when our meetup turned 2 were we able to have our first in-person event. For me and my co-hosts it was a great learning experience to understand how to successfully organize an in-person event, from WiFi to the amount of pizza's that a group of hungry cyber-nerds will consume. For the first event we had our great friends from Hack The Box come over and join us. Check out this after-movie to get a sense of the scale and spot my “psycho”-face somewhere in the video.

The community {#the-community}

Since the early days the community has grown so rapidly. At the time of the blog post we are well past the 1800 members and we are growing to 1900! Of course these are not all people that show up at every meetup, but they come and go. Generally an in-peron meetup will have somewhere between 50 and 100 people attending. Our online meetups will have about 30 to 50 people RSVP-ing to join.

{{< figure src=“/ox-hugo/meetup-christmas-scaled.jpg” caption=”Figure 1: Christmas meetups require a \“foute\” christmas sweater” >}}

The community is full of regulars, people that only swing by for the in-person meetups, and people that just try to figure out if cyber security is a fit for them. I loved meeting every single one of you all. It was such an experience to see friendships grow, skill-sets expand and people step up to actually present in front of a crowd.

Making Friends {#making-friends}

The very first person to ever log into the meetup was @GevuldCookie. She entered the zoom call when I was anxiously waiting for somebody to actually show up. In the same first meetup @DutchPyro joined and after some chatting and exchanging ideas we have been organizing the discord and meetups together ever since. A few years in and @Salp joined as a co-organizer.

Earlier this year we even had our first meetup-baby being born. How special is that?

As the meetup has been online most of the time I have made friends all over the globe, from Egypt to India, from South Africa to Ottowa. It has been quite extraordinary. Charles, from the Texas meetup, dropped by in Utrecht while on holiday in Europe. I went to Lissabon and met up with Pedro and we became such good friends.

{{< figure src=“/ox-hugo/pedro.jpg” caption=”Figure 2: Meeting up with Pedro in Portugal” >}}

The list of people that have meant something to me and the meetup is extremely long, from Hack The Box we have had Soti, Kristi, Stella, Emma, Austin, Shaun, Bran and so many more. We also have had a quite large regular group of people joining every month. I started listing names here, but that entire list would be so long and I would certainly forget specific people. So just know that if you were a regular I loved the time that I spent with you!

The cost of a meetup {#the-cost-of-a-meetup}

As a meetup we were extremely lucky that NOVI flipped the bill for our in-person events. Imagine ordering 40 to 60 pizza's at your favorite place.... But the cost of organizing is much greater then just that, the amount of personal time that I have spent working on getting the meetups together, chasing down guests and getting things organized is quite something and this is exactly the point where it breaks for me.

After more then 4 years constantly worrying and working on the meetup, even planning my own family life around the meetups (I kid you not, we planned our family holidays so that they did not conflict with the meetups), it has been enough.

Being so involved has taken the joy out of Hack The Box for me, a price I have gladly paid, but a joy which I would like to get back. So for me that means stepping back, letting other people pick up a great meetup and giving it a bright new future.

The end {#the-end}

Every beginning must have an end, and an end makes way to new beginnings. So does this step. Yes, the Hack The Box meetups are over for me after the June meetup, but with NOVI I will continue working for our community. I will continue being involved in the Hack The Box community in various ways, just not organizing monthly meetups.

We (NOVI) already host a weekly hacking session on Friday morning where we tackle Hack The Box machines and other types of challenges. Besides that we organize many different types of workshops and events across the areas of our curriculum (cyber security, development and business).

In the next year we will be hosting a quarterly event dedicated to security in a broader sense, and crossing over to the development realm. These events will line up with our starting moments in the school year. You can stay in the loop by registering on our meetup (https://www.meetup.com/workshops-novi-hogeschool/) page or follow me on LinkedIn (https://www.linkedin.com/in/credmp/).

As I am stepping back from my work on the Dutch Hack The Box meetup I am looking forward to exploring new events and opportunities to teach people about this wonderful field.

Last week I was a guest on the Cyber Cafe podcast by rootsec. It was a fun discussion on education and the current xz backdoor story. It is in the Dutch language. It is available on youtube and included below:

This post is just a small note for those of you who also run Microsoft Teams on Linux through their browser and now receive a note “your browser does not meet the requirements for the new Teams”. It turns out that the client is looking at the user-agent string to determine which browsers it accepts, and which not.

So, if you have the message, install an user-agent switcher and select a common browser on a common OS (from the MS perspective) and you will suddenly meet the requirements.

This is a longer form article. I is relevant as of February 18th 2023. If the circumstances of my environment changes I will try to update this article to reflect the situation. You can find the full source code of my dotfiles on Github.

I like consistency and simplicity. I do not like to use many different tools to do different things, I rather spend my time learning to use a few tools very well then to follow the hype on the latest trend of tools for something we have been doing forever.

This philosophy I transfer to pretty much everything in life. I have been using the same laptop bag for ages, I have a small mechanical keyboard, and I run the same version of my OS on all my devices. One device for on the go, the other for at home. They look the same and act the same, courtesy of an Linux distribution called NixOS.

Below you will find 2 screenshots, one from my laptop, the other from my desktop. The only difference is the size of the screen.

{{< figure src=“/ox-hugo/desktop.png” caption=”Figure 1: My Linux desktop on my laptop” >}}

{{< figure src=“/ox-hugo/desktop-large.png” caption=”Figure 2: My Linux desktop on my desktop” >}}

NixOS {#nixos}

I use the NixOS distribution of Linux. NixOS is a wonderful operating system that works by declaring what you want your environment to be and then applying that declaration to the current version of the environment. That sounds difficult, but let me explain.

Suppose you have just installed a Linux distribution and you want to install the wonderful Emacs editor. In most distributions you will go to the package manager, search for Emacs and click on install. A few seconds later, Emacs is installed. With NixOS you edit a file that describes your environment, you will add a line to it saying that Emacs is part of your environment. When you have saved the file you will ask NixOS to create a new version of your environment, to do so it will install Emacs for you.

I say it will create a new version of your environment. This means there is an old version as well, right? Yes! NixOS has a concept of Generations. This means every change happens in its own version of the environment. So, if a change goes wrong, you just revert back to the previous version.

This sounds like a great deal of work, and it is. It is not for the new Linux user, that is for sure. If you spend some time learning NixOS I am sure you will be grateful for it. Just the other day I tried to use the wayland system on Linux, my configuration went horribly wrong and I was left with an unusable system. I rebooted the machine, selected the previous generation, and I was back where I started before the change. It is that useful!

As I share my configuration over multiple machines I split up the configuration into a machine specific version to my desktop, laptop, and the things that should run on both:

The shared configuration contains all the juice, it sets up the graphical user interface, creates users and assigns to groups. This means that when you run this configuration you will end up in a very barren i3 tiling window manager. More on that later.

Most of my applications are courtesy of something called home-manager. This is a user-space application that allows for easy changes to the environment. As none of these changes can actually wreck the environment I kept them outside of the default NixOS configuration.

My home-manager configuration takes care of installing all the user-space tools that I use. It also sets up my shell and configures the Emacs daemon.

You might wonder, do you create a configuration file every time you need a tool? No! When I just need a one-off tool I use something called nix-shell. In the screenshots above you will notice that I run neo-fetch. This program is not part of my normal system as I only use it for screenshots as the one above. Within a terminal I run it as follows: nix-shell -p neofetch --run neofetch. This will temporarily install neo-fetch and run it. Afterwards it can be cleaned up. I also do this for most of the tools, such as unzip. I only install then when I need them. This keeps everything that is installed very clean.

You might also notice that there are not programming language toolchains in my configuration. That is correct. When I have a programming project I use something called direnv, see the direnv webpage for some background.

Whenever I start a new programming project I run the following command in the project root: nix --extra-experimental-features "nix-command flakes" flake new -t github:nix-community/nix-direnv .. This will create a flake.nix file in which I can declare what this project needs as dependencies. As the rest of my environment is extremely clean, I will need to specify precisely what is needed. Take the listing below, it is part of a programming project in which I use Rust, Golang, Python and Java. Whenever I move into this project, all the tools will be installed. This also means that it works exactly the same on every single system where I use this setup.

{
  description = "A basic flake with a shell";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      devShells.default = pkgs.mkShell {
        packages = with pkgs; [
          pkg-config
          openssl.dev
          cargo
          rustc
          rustfmt
          clippy
          rust-analyzer
          aoc-cli
          go
          gopls
          gotools
          govulncheck
          pkgs.jdk
          pkgs.jdt-language-server
          pkgs.python311
        ];
        # Environment variable specifying the plugin directory of
        # the language server 'jdtls'.
        JDTLS_PATH = "${pkgs.jdt-language-server}/share/java";
      };
    });
}
Code Snippet 1: A nix-direnv declaration for a polyglot programming project

This might seem like a hassle. It is true, it is more work then just installing Golang on Ubuntu and “just having it”. But once you use multiple systems or work together in groups you will start appreciating it, trust me.

i3 {#i3}

As I like simplicity I tend to not use elaborate windowing environments, such as Gnome or KDE. I try them out every once in a while, but I also go back to i3. Back in the day I ran enlightenment, but now I have been using i3 WM for quite some years. My configuration is quite mature and I generally only change it when I want to add a new tool to my daily use, or when tools get good updates such as polybar. The configuration is part of my dotfiles.

When I boot my system all I have is a top bar that contains the following information:

  • 💻 Active workspaces (each has its own icon and use)
  • 💾 Current fill state of my disks
  • 🛡️ VPN status
  • 🔊 Sound and its volume percentage
  • 🛜 Wifi state (laptop only)
  • 🔋 Battery state (laptop only)
  • ⏰ Time
  • 📥 Tray icons (flameshot, bluetooth and nextcloud)

That is it. After all those years working with computers, that is all I really need. If I could I would write a toggle for the bar as well, to only show up when needed. The very appealing thing about i3 is its tiling feature. I will never have windows that overlap. Everything is neatly ordered in workspaces and within workspaces in columns or rows. As I create dedicated workspaces everything has a specific place:

  1. Terminal (alacritty with tmux)
  2. Emacs
  3. Virtual Machines
  4. Firefox
  5. Chrome

From workspace 6 on I consider them “throw-away” workspaces. The things I will store there will be used only shortly. The exception is workspace 10 (or 0). This contains my Spotify.

To launch applications I use something called Rofi. It is a window switcher, application launcher and menu replacement tool. It is very easy to customize and you can make it exactly what you want. My configuration is available on github.

{{< figure src=“/ox-hugo/rofi.png” caption=”Figure 3: Rofi launching applications in i3” >}}

You can configure your environment exactly as you want. Take a look at r/unixporn for some more extreme versions of customized desktops.

#emacs #development #writing

It has been a little while. I have been swamped with work and the work on my thesis, leaving no room to finish the Advent of Code or much of anything else.

Yesterday I gave my practice presentation for my thesis. This means I am one more step closer to the finish line. During the day there were many interactions with fellow students. One of the topics has been the templates to use at Open Universiteit. So, I thought I would just create a repository of the templates that I use, so that anyone can learn from them.

The repository is here: https://github.com/credmp/ou-templates

The weekend generally is a place to find hard puzzles again, this time not so much. A simple quest to find the next number in a sequence with a fully written out algorithm to follow. They key here is to use recursion.

package main

import (
	"fmt"
	"time"

	"arjenwiersma.nl/aoc/internal/aoc"
)

func NextStep(in []int) int {
	allZero := true

	for _, v := range in {
		if v != 0 {
			allZero = false
		}
	}

	if allZero {
		return 0
	}

	var diffs []int
	for i := 1; i < len(in); i++ {
		diffs = append(diffs, in[i]-in[i-1])
	}

	p := NextStep(diffs)
	return in[len(in)-1] + p
}

func main() {
	content := aoc.AsLines("2023/Day09/input.txt")

	var lines [][]int
	for _, v := range content {
		lines = append(lines, aoc.AsNumbers(v))
	}

	startTime := time.Now()

	var res []int
	for _, v := range lines {
		res = append(res, NextStep(v))
	}

	r := aoc.SumArray(res)
	endTime := time.Now()
	elapsed := endTime.Sub(startTime)
	fmt.Printf("Part 1: %d (%v)\n", r, elapsed)

	startTime = time.Now()

	for _, v := range lines {
		aoc.Reverse(v)
	}

	res = []int{}
	for _, v := range lines {
		res = append(res, NextStep(v))
	}

	r = aoc.SumArray(res)
	endTime = time.Now()
	elapsed = endTime.Sub(startTime)
	fmt.Printf("Part 2: %d (%v)\n", r, elapsed)
}

Somewhat suspicious of 2 easy days we end up at Day 8. A simple map to follow again, from one key follow the instructions until we hit ZZZ. Part 2 had us do it for several keys at once, with the goal to find the spot where they all converge. This can take forever, erhm, a long time.

So there has to be a math type solution to this problem. It turns out to be a Least Common Multiple problem. It is the smallest positive integer that is divisible by two or more numbers without leaving a remainder. To find the LCM of two or more numbers, you can use a method called prime factorization or a simpler approach involving multiples. We can also use the Greatest Common Divisor (GCD) to find the LCM.

LCM(a, b) = (a * b) / GCD(a, b)

Example: Find the LCM of 12 and 18 using their GCD.

Step 1: Find the GCD of 12 and 18.

  • You can use methods like prime factorization or the Euclidean algorithm to find the GCD.
  • GCD(12, 18) = 6

Step 2: Use the formula to find the LCM.

LCM(12, 18) = (12 * 18) / 6 = 216 / 6 = 36

So, the LCM of 12 and 18 is 36.

package main

import (
	"fmt"
	"strings"
	"time"

	"arjenwiersma.nl/aoc/internal/aoc"
)

func LCM(numbers []int) int {
	result := numbers[0]
	for i := 1; i < len(numbers); i++ {
		result = (result * numbers[i]) / GCD(result, numbers[i])
	}
	return result
}

func GCD(a, b int) int {
	if b == 0 {
		return a
	}
	return GCD(b, a%b)
}

func solve(s string, instr string, m map[string][]string, p2 bool) int {
	steps := 0
	for {
		d := 0
		if string(instr[steps%len(instr)]) == "R" {
			d = 1
		}

		if !p2 && s == "ZZZ" {
			break
		}
		if p2 && s[2] == 'Z' {
			break
		}

		s = m[s][d]

		steps += 1
	}
	return steps
}
func main() {
	content := aoc.AsLines("2023/Day08/input.txt")

	instr := content[0]
	m := make(map[string][]string)

	for _, v := range content[2:] {
		n := strings.Split(v, " = ")
		lr := strings.Split(n[1], ",")

		m[n[0]] = []string{strings.TrimSpace(lr[0][1:]), strings.TrimSpace(lr[1][:len(lr[1])-1])}
	}

	startTime := time.Now()

	steps := solve("AAA", instr, m, false)

	endTime := time.Now()
	elapsed := endTime.Sub(startTime)
	fmt.Printf("Part 1: %d (%v)\n", steps, elapsed)

	startTime = time.Now()

	var solves []int
	for k := range m {
		if k[2] == 'A' {
			solves = append(solves, solve(k, instr, m, true))
		}
	}

	// do stuff
	endTime = time.Now()
	elapsed = endTime.Sub(startTime)
	fmt.Printf("Part 2: %d (%v)\n", LCM(solves), elapsed)
}

Today we learned about CamelCards, a game of poker meant to play on the back of a camel. The most interesting part here was the parsing of the cards and figuring out how to properly rank them. Part 2 turned out to be as easy as tracking Jokers.

package main

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"arjenwiersma.nl/aoc/internal/aoc"
)

type Card struct {
	bid    int
	hand   []int
	jokers int
}

func (c *Card) strongerThen(o *Card) bool {
	for i, v := range c.hand {
		if v > o.hand[i] {
			return true
		} else if v < o.hand[i] {
			return false
		}
	}
	return false
}

func (c *Card) rank() int {
	freq := make([]int, 15)
	for _, v := range c.hand {
		if v == 1 { // skip counting the joker
			continue
		}
		freq[v]++
	}

	sort.Ints(freq)

	freq[len(freq)-1] += c.jokers
	strength := 2 * freq[len(freq)-1]
	// full house and 2 pair
	if freq[len(freq)-2] == 2 {
		strength += 1
	}
	return strength
}

func NewCard(s string, bid int, p2 bool) *Card {
	c := &Card{}
	c.bid = bid
	c.jokers = 0
	for p := 0; p < len(s); p++ {
		if s[p]-'0' >= 2 && s[p]-'0' <= 9 {
			c.hand = append(c.hand, int(s[p]-'0'))
		} else {
			x := 10
			switch s[p] {
			case 'A':
				x = 14
			case 'K':
				x = 13
			case 'Q':
				x = 12
			case 'J':
				if p2 {
					c.jokers += 1
					x = 1
				} else {
					x = 11
				}
			case 'T':
				x = 10
			}
			c.hand = append(c.hand, x)
		}
	}
	return c
}

func (c *Card) String() string {
	return fmt.Sprintf("%v (%d)", c.hand, c.bid)
}

func main() {
	content := aoc.AsLines("2023/Day07/input.txt")

	var cards []*Card
	for _, v := range content {
		p := strings.Split(v, " ")
		b, _ := strconv.Atoi(p[1])
		c := NewCard(p[0], b, false)
		cards = append(cards, c)
	}

	startTime := time.Now()
	lessFunc := func(i, j int) bool {
		if cards[i].rank() == cards[j].rank() {
			return cards[j].strongerThen(cards[i])
		}
		return cards[i].rank() < cards[j].rank()
	}

	sort.Slice(cards, lessFunc)

	res := 0
	for i, c := range cards {
		res += (i + 1) * c.bid
	}

	endTime := time.Now()
	elapsed := endTime.Sub(startTime)
	if 251216224 != res {
		panic("Wrong answer")
	}
	fmt.Printf("Part 1: %d (%v)\n", res, elapsed) // 251216224

	cards = []*Card{}
	for _, v := range content {
		p := strings.Split(v, " ")
		b, _ := strconv.Atoi(p[1])
		c := NewCard(p[0], b, true)
		cards = append(cards, c)
	}
	startTime = time.Now()

	sort.Slice(cards, lessFunc)

	res = 0
	for i, c := range cards {
		res += (i + 1) * c.bid
	}
	endTime = time.Now()
	elapsed = endTime.Sub(startTime)
	if 250825971 != res {
		panic("Wrong part 2")
	}
	fmt.Printf("Part 2: %d (%v)\n", res, elapsed) // 250825971
}

Day 6 turned out to be the easiest day in the range so far. A simple implementation of the algorithm was more than sufficient.

I later learned that it was a quadratic function. On the subreddit Deatranger999 said:

If you hold down the button for x seconds, then you will beat the distance if the quadratic x^2 – t x + d is at most 0, where t is the total time of the race and d is the distance you'd like to beat. So I just plugged each one into WolframAlpha, found the roots, and then calculated the number of integers between the two roots.

My solution was to bruteforce :)

package main

import (
	"fmt"
	"strconv"
	"strings"

	"arjenwiersma.nl/aoc/internal/aoc"
)

func main() {
	lines := aoc.AsLines("2023/Day06/input.txt")

	var times []int
	for _, t := range strings.Split(lines[0], " ")[1:] {
		s := strings.TrimSpace(t)
		if s == "" {
			continue
		}
		i, _ := strconv.Atoi(s)
		times = append(times, i)
	}
	var distances []int
	for _, t := range strings.Split(lines[1], " ")[1:] {
		s := strings.TrimSpace(t)
		if s == "" {
			continue
		}
		i, _ := strconv.Atoi(s)
		distances = append(distances, i)
	}

	result := make([]int, len(times))

	for i := 0; i < len(times); i++ {
		for t := 0; t < times[i]; t++ {
			d := t * (times[i] - t)
			if d > distances[i] {
				result[i] += 1
			}
		}
	}

	ans := 1
	for _, c := range result {
		ans *= c
	}

	fmt.Println("Part 1: ", ans)

	nT, _ := strconv.Atoi(fmt.Sprintf("%d%d%d%d", times[0], times[1], times[2], times[3]))
	nD, _ := strconv.Atoi(fmt.Sprintf("%d%d%d%d", distances[0], distances[1], distances[2], distances[3]))

	ans = 0
	for t := 0; t < nT; t++ {
		d := t * (nT - t)
		if d > nD {
			ans += 1
		}
	}
	fmt.Println("Part 2: ", ans)

}

Today was an interesting problem. We are basically given a map to follow based on a number, possibly transforming the number at each step. With a single number this is quite simple, just apply the rules and step through each set of transformations. The problem becomes tricky when it turns out we have to deal with enormous ranges of numbers. On the subreddit some people reported their implementation to take hours and use 20GB of memory.

Luckily there is always a fast solution. In this case it was using ranges of numbers to go through the transformations, so just taking the first number and then creating a (new) range out of the transformation instead of each individual number.

package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"strconv"
	"strings"
	"time"

	"arjenwiersma.nl/aoc/internal/aoc"
)

type Range struct {
	d, s, r int
}

func (r *Range) transform(i int) int {
	if i >= r.s && i <= r.s+r.r-1 {
		delta := i - r.s
		return r.d + delta
	}

	return i
}

type Segment struct {
	from, to int
}

func main() {
	content, _ := os.ReadFile("2023/Day05/input.txt")
	segments := strings.Split(strings.TrimSpace(string(content)), "\n\n")

	seedStr := strings.Split(segments[0][6:], " ")
	var seed []int
	for _, x := range seedStr {
		if strings.TrimSpace(x) == "" {
			continue
		}
		s, err := strconv.Atoi(strings.TrimSpace(x))
		if err != nil {
			log.Fatal(s, err)
		}
		seed = append(seed, s)
	}
	// fmt.Println("Seeds: ", seed)

	maps := make([][]Range, len(segments)-1)
	for i, s := range segments[1:] {
		l := strings.Split(s, "\n")
		maps[i] = make([]Range, len(l)-1)
		for j, x := range l[1:] {
			var m Range
			fmt.Sscanf(x, "%d %d %d", &m.d, &m.s, &m.r)
			maps[i][j] = m
		}
	}

	startTime := time.Now()

	min := math.MaxInt
	for _, s := range seed {
		c := s
	trans:
		for _, m := range maps {
			for _, t := range m {
				source := c
				c = t.transform(c)
				if c != source {
					continue trans
				}
			}
		}
		if c < min {
			min = c
		}
	}
	endTime := time.Now()
	elapsed := endTime.Sub(startTime)
	fmt.Printf("Part 1: %d (%v)\n", min, elapsed) // 662197086

	startTime = time.Now()
	// starting segments
	var S []Segment
	for i := 0; i < len(seed); i += 2 {
		S = append(S, Segment{seed[i], seed[i] + seed[i+1]})
	}

	for _, m := range maps {
		var A []Segment
		for _, t := range m {
			var nS []Segment
			for _, s := range S {
				nA, nnS := createSegments(s, t)
				A = append(A, nA...)
				nS = append(nS, nnS...)
			}
			S = nS
		}
		S = append(S, A...)
	}
	min = math.MaxInt

	for _, s := range S {
		min = aoc.Min(s.from, min)
	}
	endTime = time.Now()
	elapsed = endTime.Sub(startTime)
	fmt.Printf("Part 2: %d (%v)\n", min, elapsed) // 52510809
}

func createSegments(s Segment, t Range) (A []Segment, nS []Segment) {
	before := Segment{s.from, aoc.Min(s.to, t.s)}
	inter := Segment{aoc.Max(s.from, t.s), aoc.Min(t.s+t.r, s.to)}
	after := Segment{aoc.Max(t.s+t.r, s.from), s.to}

	if before.to > before.from {
		nS = append(nS, before)
	}
	if inter.to > inter.from {
		inter.from = inter.from - t.s + t.d
		inter.to = inter.to - t.s + t.d
		A = append(A, inter)
	}
	if after.to > after.from {
		nS = append(nS, after)
	}

	return A, nS
}