Implement the Enigma Machine The Enigma machine is a fairly complex cipher machine used by the Germans and others to encrypt their messages.  It is your job to implement this machine*.
Step 1, Rotation
Our enigma machine has 3 slots for rotors, and 5 available rotors for each of these slots. Each rotor has 26 different possible positions (from A to Z).  Each rotor has a predefined notch position:
Rotor  Notch
------------
1      Q
2      E
3      V
4      J
5      Z

On keypress the following steps occur:

The rotor in Slot 1 rotates
If the rotor in Slot 1 moves past its notch, then it rotates the rotor in Slot 2.
If the rotor in Slot 2 is in its notch (but didn't just move there), both rotor 2 and 3 rotate once.

If we are using rotors 1,3,5 and they are in positions P,U,H then the sequence of positions is: P,U,H &gt; Q,U,H &gt; R,V,H &gt; S,W,I
Step 2, Substitution
Each of the rotors performs a simple character substitution.  The following is a chart of each of the rotors in the A position:
  ABCDEFGHIJKLMNOPQRSTUVWXYZ
  --------------------------
1 EKMFLGDQVZNTOWYHXUSPAIBRCJ
2 AJDKSIRUXBLHWTMCQGZNPYFVOE
3 BDFHJLCPRTXVZNYEIWGAKMUSQO
4 ESOVPZJAYQUIRHXLNFTGKDCMWB
5 VZBRGITYUPSDNHLXAWMJQOFECK
R YRUHQSLDPXNGOKMIEBFZCWVJAT

Rotor 1 in position T is PAIBRCJEKMFLGDQVZNTOWYHXUS, which would substitute the letter C for I.
After the three rotors perform their substitution, the reflector is hit (listed as R above).  It performs its own substitution, and then reflects the signal back through the rotors.  The rotors then perform a reverse substitution in reverse order.
Reverse substitution means that instead of Rotor 1 substituting A with E, it substitutes E with A
Slots are filled with rotors 1,2,3 all in position A.  The letter Q follows the path Q&gt;X&gt;V&gt;M through the rotors.  M reflects to O, which then follows the reverse path of O&gt;Z&gt;S&gt;S.  Therefore, A is substituted with S.
Input/Output
You are passed:

A list of 3 rotors (as integers)
A list of 3 starting rotor positions (as letters)
A string that needs to be encrypted.

You can assume that your input will be well formed, and all characters will be uppercase letters, no spaces.
You must return the encrypted string.
You can optionally accept the rotors, notches, and reflectors as input.  For those that don't can take off 95 bytes from their score, as 95 = ceil(log2(26 letters ^(26*6 rotors +5 notches))/8 bytes)
Test cases
Rotor Position Input              Output
4,1,5 H,P,G    AAAAAAAAA          PXSHJMMHR
1,2,3 A,A,A    PROGRAMMINGPUZZLES RTFKHDOCCDAHRJJDFC
1,2,3 A,A,A    RTFKHDOVZSXTRMVPFC PROGRAMRXGVGUVFCES
2,5,3 U,L,I    GIBDZNJLGXZ        UNCRAUPSCTK

My implementation can be found on Github.  I've tested it, but I may have bugs in my implementation (which would mean that my test cases are likely wrong).
*I've tried to make this as accurate as possible, but due to the variations between machines, I may have some details wrong.  However, your task is to implement what I've described, even if I'm inaccurate.  I'm not including the plugboard for simplicity
Jelly, 42 38 37 bytes
=&quot;⁵Ḣ€Ṗ1;ṙ@&quot;µɼ⁶ṭØAiịɗƒµ®Ṛi@ịØAɗƒ
ḷ⁹©Ç€

Try it online!
Verify each test case
This is extremely liberal with the input format. It takes input in the following way:

The three rotors (the strings, not the numbers) as the first command line argument, each rotated so that the starting points are the first characters
The message as the second command line argument
The 3 specific notches as the third command line argument
The reflector as the fourth command line argument

In the above TIO link, the Footer converts a triple of numbers (the rotors) into the rotor strings, then rotates them to the correct order, so that you don't have to do it yourself.
However, for 45 bytes we can have a program which takes input in the following way:

The message, on STDIN
The three rotors (as the strings) as the first command line argument
The 3 specific notches as the second command line argument
The reflector as the third command line argument
The three starting points for the rotors as the fourth command line argument

Again, the Footer converts the rotors from integers to the corresponding strings.
Finally, if we want to go all the way and just accept 3 inputs (the rotors, as integers, the starting positions and the message (thus have -95 to our score), we get a score of 44

How it works
As they all use the same underlying algorithm, I'll just explain the 38 byte version in full, and I'll cover how the other two versions adapt the inputs to match the shortest version. For this explanation: M is the message, R are the three rotor strings, N are the notches, F is the reflector and C is the value in the register (initially 0).
ḷ⁹©Ç€ - Main link. Takes M on the left and R on the right
 ⁹    - Yield R
  ©   - And copy it into the register. C = R
ḷ     - Discard R and yield M
    € - Over each character in M:
   Ç  -   Call the helper link
        Implicitly output the final result

=&quot;⁵Ḣ€Ṗ1;ṙ@&quot;µɼ⁶ṭØAiịɗƒµ®Ṛi@ịØAɗƒ - Helper link. Takes a character X on the left

=&quot;⁵Ḣ€Ṗ1;ṙ@&quot;µɼ                   - Rotate the rotors
            ɼ                   - Yield C, run the following on it, save the result to C:
           µ                    -   Initially, C = R, the list of rotors
  ⁵                             -   Yield the notches, N
 &quot;                              -   Pair each notch with each rotor, then:
=                               -     Vectorised equality
   Ḣ€                           -   Take the head of each
                                      This yields a triple of bits [a, b, c]
                                      which indicate which rotor hits its notch
     Ṗ                          -   Remove c as rotor 3 has no affect
      1;                        -   Prepend 1 as rotor 1 always moves; [1, a, b]
          &quot;                     -   Pair 1 with rotor 1, a with rotor 2 and b with rotor 3,
                                        then for each pair:
        ṙ@                      -     Rotate the rotor if the bit is 1

             ⁶ṭØAiịɗƒ           - Send the character through the rotors
             ⁶                  - Yield the reflector F
              ṭ                 - Tack it to the end of the rotors, call this C'
                   ɗ            - Group the previous 3 links into a dyad f(U, L):
                                   where U is a character and L a list of characters
               ØA               -   &quot;ABC...XYZ&quot;
                 i              -   Index of U in the alphabet
                  ị             -   Index into L
                    ƒ           - Starting with X, reduce C' by the dyad f(U, L)
                                  As C is a triple of lists of characters, this returns
                                  A = f(f(f(f(X, C[1]), C[2]), C[3]), F), a single character

                     µ®Ṛi@ịØAɗƒ - Send A back through the rotors
                     µ          - Begin a new link with A as the argument
                             ɗ  - Group the previous 3 links into a dyad g(U, L)
                        i@      -   Index of U in L, i
                          ịØA   -   i'th letter of the alphabet
                      ®         - Yield C
                       Ṛ        - Reverse C
                              ƒ - Starting with A, reduce rev(C) by the dyad g(U, L)
                                      This yields g(g(g(A, C[3]), C[2]), C[1]),
                                      which is our intended result

How the other 2 work
The 45 byte version
Taking a look at the code, we notice that the only thing that's changed is that the last line is now
ṙ&quot;©⁶O_65¤ṛɠÇ€

The difference between this version and the 37 byte version is how the rotors are taken as input:

The three rotors (the strings, not the numbers) as the first command line argument, each rotated so that the starting points are the first characters


The three rotors (as the strings) as the first command line argument

The new bits here just rotate the rotors for us and take M from STDIN:
ṙ&quot;©⁶O_65¤ṛɠÇ€ - Main link. Takes R on the left
        ¤     - Create a nilad:
   ⁶          -   The 3 starting points of the rotors
    O         -   Converted to char points
     _65      -   Minus 65
 &quot;            - Zip with each rotor:
ṙ             -   Rotate the rotor that many steps
  ©           - Save this to the register
          ɠ   - Read M from STDIN
         ṛ    - Discard the rotors and yield M
           Ç€ - Call the helper link on each character of M

The 139 byte version
Let's take a look at the full code:
=&quot;³ị“QEVJZ”¤Ḣ€Ṗ1;ṙ@&quot;µɼ“¡ƝḋœṚṗḶw⁸Aß`’œ?ØA¤ṭØAiịɗƒµ®Ṛi@ịØAɗƒ
ị“F.⁻wṣḊ£tọḅɱ“¥AṡḌỴịk⁼UH9“Ñɱ½#Ẋʋẹ¹⁼UṢ“KzBpñÇḅẊẎḳḣ“¡j5ɼ}ṡỴb\£BṆ’œ?ØAṙ&quot;©⁴O_65¤ṛɠÇ€

Try it online!
This is very similar to our 45 byte version. In fact, here's what's different in the first lines, with everything that is unchanged, replaced with .
..³ị“QEVJZ”¤..........“¡ƝḋœṚṗḶw⁸Aß`’œ?ØA¤.................

Furthermore, you'll note that the end of the first line is ṙ&quot;©⁴O_65¤ṛɠÇ€, which is the first line of the 45 byte version, meaning that there are really only three changes. In this case, those changes are because of the changes to the input system. Here's the first 2:

⁴ \$\to\$ ³ị“QEVJZ”¤. Rather than take the notches as input, we use the rotor list (³) to ịndex into the string of notches “QEVJZ”. The ¤ is simply a precedence marker, telling the program to treat this as a constant

⁵ \$\to\$ “¡ƝḋœṚṗḶw⁸Aß`’œ?ØA¤. Again, this is changing an input into program data, the reflector in this case. ¤ Once again acts as a precedence marker. However, we have 3 new commands here:

œ? is a dyad which takes 2 arguments - on the left, an integer x, and on the right a string, s - and returns the x'th permutation of s
ØA we've already encountered, and is the uppercase alphabet. In this context, it acts as the right argument to œ?
“¡ƝḋœṚṗḶw⁸Aß`’ is the compressed integer \$383316524290458478707255597\$, which is the left argument to œ?

Together, this returns the \$383316524290458478707255597\$th permutation of the uppercase alphabet, or the string &quot;YRUHQSLDPXNGOKMIEBFZCWVJAT&quot;


The final change is the entire start of the last line:
ị“F.⁻wṣḊ£tọḅɱ“¥AṡḌỴịk⁼UH9“Ñɱ½#Ẋʋẹ¹⁼UṢ“KzBpñÇḅẊẎḳḣ“¡j5ɼ}ṡỴb\£BṆ’œ?ØA

Here, we have a list of massive compressed numbers:
“F.⁻wṣḊ£tọḅɱ“¥AṡḌỴịk⁼UH9“Ñɱ½#Ẋʋẹ¹⁼UṢ“KzBpñÇḅẊẎḳḣ“¡j5ɼ}ṡỴb\£BṆ’

Each “ marks the beginning of a new compressed number, and the entire list is
[67892310845892591685803413, 5023890671354916271018308, 16834429128340685633834184, 72949485053238139997554738, 340670280577577536609579431]

We then take the three integers which correspond to the three rotor inputs with ị, and then use œ?ØA to generate the alphabet permutations we want for each rotor. Finally, these permutations are fed into the rest of the program, already covered above.