text:
Pass on your radiation
The task here is pretty simple.  You should write a program that takes a string as input and outputs it (that is a cat program).
Additionally when the \$n\$th byte of your program is removed (without replacement) the augmented program should take a string as input and remove the \$n\$th character.  If the input to your augmented program is less than \$n\$ characters long it should return it as is. For example the python program
x = input()
if "y" == "y":
  print(x)
else:
  print(x[:16] + x[17:])

Try it online!
will remove the \$17\$th character if its \$17\$th byte is removed (the first y).
Scoring
Your answer's score will be the number of bytes for which removal does not cause the program to remove the corresponding byte.  With better score being lower.
In our example above the total length is 68 bytes, with one byte (the \$16\$th one) augmenting the program correctly.  That leaves a score of 67 bytes.
If the program were modfied to work in two places like so:
x=input()
u="az"
if u!="az":x=x[:13+(u<"c")]+x[14+(u<"c"):]
print(x)

Try it online!
Where removing the \$14\$th and \$15\$th characters (a and z) work properly.  The score is 66 bytes or 68 - 2, since the program is 68 bytes long but two of those bytes work properly.
Rules and clarifications

There must be at least one byte which can be removed that causes the program to remove the corresponding character in the input.  This means a cat program on its own is not a valid submission.
Only one byte will be removed at a time.  You do not have to worry about what happens when multiple bytes are removed from your program at the same time.
You should support strings using printable ascii characters in addition to all characters that appear in your program itself.  Input containing anything else is undefined behavior.

May we work on arrays of characters instead of strings? (For languages for which it's not the same thing.)

@Arnauld This challenge asks for a complete program, I chose this because I feel that source layout doesn't play very nicely with functions.  As a result I am pretty sure that input will have to be taken as a string.

In your example you set u='az' and depending on those characters the input gets trimmed, so instead of using characters can we use index like 2,3 etc...?

@game0ver Your code can be any sequence of bytes, the process is agnostic to what the code is doing.  You could set u=8, but I'm not sure why you would want to, if either the 8 or the u is removed then the program will error.

Is it OK if the output includes extra non-printable characters (e.g. null bytes)?

@RobinRyder An extra newline at the end is ok.

@WheatWizard But non-printable characters are not?

@RobinRyder I would consider the newline to be a non-printable characters,but I don't think extra null bytes are acceptable.

One more complicated scoring formula that could have worked pretty well is n+(n-r)*100/n, with n = size in bytes and r = number of removable characters.

@Arnauld I'm pretty happy with the current scoring formula as it is.

Python 2, score:  56 50  48 bytes - 6 = 42
This is more an attempt to allow to remove more than 1 or 2 characters than to achieve the lowest possible score. Given the scoring scheme, allowing one more byte to be removed this way would cost at least one extra byte, leaving the score unchanged at best.
You may remove the 3rd to the 8th character, i.e. any digit of \$545454\$.
n=545454;x=input();print x[:n%11]+x[n%11+n%9/4:]

Try it online!
How?
Removing the \$n\$th digit from \$545454\$ and applying a modulo \$11\$ results in \$n+1\$. Applying a modulo \$9\$ instead allows to figure out whether the number was changed or not.
 pattern |    x   | x mod 9 | x mod 11
---------+--------+---------+----------
  545454 | 545454 |    0    |   (8)
  _45454 |  45454 |    4    |    2
  5_5454 |  55454 |    5    |    3
  54_454 |  54454 |    4    |    4
  545_54 |  54554 |    5    |    5
  5454_4 |  54544 |    4    |    6
  54545_ |  54545 |    5    |    7

><>, 584 bytes, score 0
<|0!o? !@ !: !@ !: !- !1$ !i|!? !: !+ !@ !@ !- !) !* !e !e$ !* !+1+ !@ !( !3 !@ !* !* !d !f !: !+ !* !2 !$ != !g !2 !$ !@ != !g !1 !$ !@ !: !g !0 !: !: !: !: !:<0~!?g1*e e-10 ~!?g0-$0:-*e e0 0</
<|0!o? !@ !: !@ !: !- !1$ !i|!? !: !+ !@ !@ !- !) !* !e !e$ !* !+1+ !@ !( !3 !@ !* !* !d !f !: !+ !* !2 !$ != !g !2 !$ !@ != !g !1 !$ !@ !: !g !0 !: !: !: !: !:<0~!?g1*e e-10 ~!?g0-$0:-*e e0 0</
<|0!o? !@ !: !@ !: !- !1$ !i|!? !: !+ !@ !@ !- !) !* !e !e$ !* !+1+ !@ !( !3 !@ !* !* !d !f !: !+ !* !2 !$ != !g !2 !$ !@ != !g !1 !$ !@ !: !g !0 !: !: !: !: !:<0~!?g1*e e-10 ~!?g0-$0:-*e e0 0</

Try it online! Verification of perfect score (note times out)
The first zero score program. This is achieved by having three copies of the program on separate lines, with only non-irradiated versions being executed. The program checks for outliers (like newlines being removed), then checks each character to see if it is the same for all three lines. If one is different, we determine which line it is and offset the current index by a multiple of 195 (the length of each line). After determining which  byte is irradiated (with no byte being -1 and the first byte being 1), we enter the output loop, decrementing the counter each time we output a character. When the counter hits zero, we skip that character, but don't reset the counter so it can be proven that only one byte of input is removed when the program is irradiated, and none when it is not.
The TIO link above has the program itself in the input, so it is easy to fiddle about in yourself. Simply try removing a byte from the program itself and the same byte will be removed from the output.
It shouldn't be too hard to golf, especially the loop in the middle that's just 60% no-ops, though I just want to post it since it is actually working and this isn't actually code-golf.

sed, Perl 5 -p, Perl 6 -p, 6 bytes, score 5
#s/.//

Try it online!
Works as sed, Perl 5 and Perl 6 program. The 1st byte is removable.

Jelly, 2 bytes, score = 1
ḷḊ

Try it online!
A full program that takes a string and prints a string. When the first byte is removed, removes the first byte of the input.
Achieving a lower score would mean finding a solution where removing any byte leads to the corresponding byte being removed from the string, which I think will be challenging.
Explanation
ḷ  | Left argument of input string and:
 Ḋ | - Input string with first byte removed

With first byte removed:
Ḋ | Remove first byte

Brainfuck, 11 bytes, score = 10
-+[,>],[.,]

Try it online!
Explanation
-+[,>]   This does nothing.
,[.,]    This reads and outputs the user's input until the program ends.

With first byte removed:
+[,>]    Reads the first character of user input, then 'discards' it by moving to a different cell.
,[.,]    This reads and outputs the user's input until the program ends.

05AB1E, score 2, 3 bytes
D¦r

Look ma, (sort of) no unicode!
The character to remove is the first one.
Try it online
Explanation:
D  push two copies of the input onto the stack
¦  remove the first character from the string on top of the stack. Alternatively, if D is removed, push the input with its first character removed onto the stack.
r  reverse the stack
implicitly, output the top of the stack

The language's name is 05AB1E, not O5AB1E. Also, the second character is ¦, not ASCII |, so it's not really "no unicode".

@Grimmy thanks, I didn't notice. Since broken pipe is in many of the small extended ascii encodings, I think it sorta counts as "no unicode", maybe?

Technically every golfing language with a SBCS is "no unicode", since they have their own encodings.

Technically the 05AB1E codepage is a subtle extension of extended ASCII.

R, 93 bytes, score=87
T<-3.5->T;s=utf8ToInt(scan(,""));if(i<-match(2*T,c(0,-5,2,1,70,6,0,5),0))s=s[-i];intToUtf8(s)

Try it online!
Like Arnauld, I tried to get a few deletable bytes rather than optimize the score, and also to use different tricks depending on the byte removed. Characters 2, 3, 4, 5, 6 and 8 can be removed.
In the unaltered version, the code makes two attempts to assign a value to T, using both leftwards <- and rightwards -> assignment. Since T is initialized as 1 (when coerced to integer), it can be assigned 7 different values, depending on the byte removed:

T<-3.5->T leads to T=3.5
T-3.5->T leads to T=1-3.5=2.5
T<3.5->T leads to T=1, since 1<3.5 is true (the same would occur if we removed byte 7 - I searched for a way to have different behaviours when removing bytes 3 and 7 but couldn't manage)
T<-.5->T leads to T=.5
T<-35->T leads to T=35
T<-3.->T leads to T=3.
T<-3.5-T leads to T=3.5-1=2.5

The statement with if and match then removes the relevant character. The rest is the usual fluff required whenever R has to handle strings.

It is obviously possible to make the number 3.5 longer (e.g. 43.5) and be allowed to remove the corresponding characters. For the sake of it, here is a different way of getting more removable characters:
R, 120 bytes, score=112
T<-3.5->T;s=utf8ToInt(scan(,""));if(i<-match(T+T,c(0,-5,2,1,70,6,0,5,rep(0,37),3.5,rep(0,47),-7),0))s=s[-i];intToUtf8(s)

Try it online!
In addition to the characters listed above, bytes 46 and 94 can be removed:

byte 46: the first T in T+T becomes +T, which matches 3.5
byte 94: -7 becomes 7, which then matches T+T=7.

Haskell, 31 - 1 = 30
Removing the first byte works
ss(x:b)=b
s x=x
main=interact s

Try it online!
Explanation
In its default state this program defines two functions ss which is unused, and s which is the identity.  It then declares that interaction is handled by s, making it a cat.
When we remove the first byte ss is renamed to s.  Making it a special case of our function s, since this is the first declaration it takes highest precedence.  So now s is defined such that if it can remove the first character it does so otherwise it falls back onto our old definition of s1 and becomes the identity. We still interact with s (although now modifed) so the entire program has the behavior of removing the first character if there is one.

1 This is important in the case of the empty string.

Keg -ir, 3 bytes, score: 2
#_^

Try it online!
Explanation
#_^ # A line comment
    # Copies the input to the output straightforwardly

    # Implicit reversed input
_   # Discard the first character in the string
 ^  # Reverse the stack

Even better: ^_^

@connectyourcharger You know, one of my eyes are sore...

@connectyourcharger ugh. Now I can't not see it as a face. :P

05AB1E, score 1, 2 bytes
q¦

Try it online!
First byte is removable.

[Python 3], 9097 bytes, score 6
eEzfFzeEz=aAzcCzaAz=aAzbBz=bBzaAz=aAzcCz=eEzfFz=fFzaAz=eEzaAz=dDzaAz=exe=exc=xec=xecexec=aAz=aA=az=Az=aAz=fr='"1"#\nimport base64\n\ndef actual_source():\n\treturn open(eval("_"+"_file_"+"_") ).read()\n\ndef decode(s):\n\treturn str(base64.b64decode(s.replace("_", str() ) ), "utf-8" )\n\ndef encode(s):\n\ts=str(base64.b64encode(bytes(s, "utf-8") ),"utf-8")\n\tresult = str().join([cur if prev!=cur else "_"+cur for prev,cur in zip([None]+list(s),s)])\n\treturn result\n\ndef get_template():\n\treturn decode("aW1wb3J0IGJhc2U2NAoKZGVmIGFjdHVhbF9zb3VyY2UoKToKCXJldHVybiBvcGVuKGV2YWwoIl8iKyJfZmlsZV8iKyJfIikgKS5yZWFkKCkKCmRlZiBkZWNvZGUocyk6CglyZXR1cm4gc3RyKGJhc2U2NC5iNjRkZWNvZGUocy5yZXBsYWNlKCJfIiwgc3RyKCkgKSApLCAidXRmLTgiICkKCmRlZiBlbmNvZGUocyk6CglzPXN0cihiYXNlNjQuYjY0ZW5jb2RlKGJ5dGVzKHMsICJ1dGYtOCIpICksInV0Zi04IikKCXJlc3VsdCA9IHN0cigpLmpvaW4oW2N1ciBpZiBwcmV2IT1jdXIgZWxzZSAiXyIrY3VyIGZvciBwcmV2LGN1ciBpbiB6aXAoW05vbmVdK2xpc3QocykscyldKQoJcmV0dXJuIHJlc3VsdAoKZGVmIGdldF90ZW1wbGF0ZSgpOgoJcmV0dXJuIGRlY29kZSgiQjY0X0VOQ09ERURfUEFSVFMiKQoKZGVmIGdldF93cmFwZXIoKToKCXJldHVybiBkZWNvZGUoIldSQVAiKQoKZGVmIGdldF9wYXlsb2FkKCk6Cgl3cmFwZXIsIHRlbXBsYXRlID0gZ2V0X3dyYXBlcigpLCBnZXRfdGVtcGxhdGUoKQoJZW5fdGVtcGxhdGVfNjQgPSBlbmNvZGUodGVtcGxhdGUpCgl3cmFwZXJfNjQgPSBlbmNvZGUod3JhcGVyKQoJcmV0dXJuIHRlbXBsYXRlLnJlcGxhY2UoIkI2NF9FTkMiKyJPREVEX1B_BUlRTIiwgZW5fdGVtcGxhdGVfNjQpLnJlcGxhY2UoIldSIisiQVAiLCB3cmFwZXJfNjQpCgpkZWYgZ2V0X29yaWdpbmFsKCk6CglwYXlsb2FkID0gZ2V0X3BheWxvYWQoKQoJd3JhcGVyID0gZ2V0X3dyYXBlcigpCglyZXR1cm4gd3JhcGVyLnJlcGxhY2UoIjEyMyIsIHJlcHIocGF5bG9hZClbMTotMV0gKQoKZGVmIG1haW4ocyk6Cglmb3IgaSwgKGEsIGIpIGluIGVudW1lcmF0ZSg_gemlwKGdldF9vcmlnaW5hbCgpLCBhY3R1YWxfc291cmNlKCkgKSApOgoJCWlmIGEgIT0gYjoKCQkJcmV0dXJuIHNbOmldICsgc1tpICsgMTpdCglyZXR1cm4gcwoKaWYgbm90IChldmFsKCJfIisiX25hbWVfIisiXyIpICE9ICJfIisiX21haW5fIisiXyIpOgoJcHJpbnQobWFpbihpbnB1dCgpICkgKQ=_=")\n\ndef get_wraper():\n\treturn decode("ZUV6ZkZ6ZUV6PWFBemNDemFBej1hQXpiQno9YkJ6YUF6PWFBemNDej1lRXpmRno9ZkZ6YUF6PWVFemFBej1kRHphQXo9ZXhlPWV4Yz14ZWM9eGVjZXhlYz1hQXo9YUE9YXo9QXo9YUF6PWZyPSciMSIjXG4xMjMnIycjJwoxMjsKYkJ6PWJCPWJ6PUJ6PWJCej1mcj0nIjEiI1xuMTIzJyMnIycKMTI7CmNDej1jQz1jej1Dej1jQ3o9ZnI9JyIxIiNcbjEyMycjJyMnCjEyOwpleGU9ZXhjPXhlYz1leGVjCmREej1kRD1kej1Eej1kRHo9YUF6LGJCeixhQXo7CmVFemZGemVFej1nR3plRXo9YUF6Y0N6PWFBejxjQ3o7CmVFej1lRT1lej1Fej1lRXo9YUF6PGNDejsKZkZ6PWZGPWZ6PUZ6PWZGej1hQXo+Y0N6OwplRXpmRno9ZUV6fGZGejsKZ0d6PWdHPWd6PUd6PWdHej1lRXp8ZkZ6OwpleGVjKGREeltnR3pdKQ=_=")\n\ndef get_payload():\n\twraper, template = get_wraper(), get_template()\n\ten_template_64 = encode(template)\n\twraper_64 = encode(wraper)\n\treturn template.replace("B64_ENC"+"ODED_PARTS", en_template_64).replace("WR"+"AP", wraper_64)\n\ndef get_original():\n\tpayload = get_payload()\n\twraper = get_wraper()\n\treturn wraper.replace("123", repr(payload)[1:-1] )\n\ndef main(s):\n\tfor i, (a, b) in enumerate( zip(get_original(), actual_source() ) ):\n\t\tif a != b:\n\t\t\treturn s[:i] + s[i + 1:]\n\treturn s\n\nif not (eval("_"+"_name_"+"_") != "_"+"_main_"+"_"):\n\tprint(main(input() ) )'#'#'
12;
bBz=bB=bz=Bz=bBz=fr='"1"#\nimport base64\n\ndef actual_source():\n\treturn open(eval("_"+"_file_"+"_") ).read()\n\ndef decode(s):\n\treturn str(base64.b64decode(s.replace("_", str() ) ), "utf-8" )\n\ndef encode(s):\n\ts=str(base64.b64encode(bytes(s, "utf-8") ),"utf-8")\n\tresult = str().join([cur if prev!=cur else "_"+cur for prev,cur in zip([None]+list(s),s)])\n\treturn result\n\ndef get_template():\n\treturn decode("aW1wb3J0IGJhc2U2NAoKZGVmIGFjdHVhbF9zb3VyY2UoKToKCXJldHVybiBvcGVuKGV2YWwoIl8iKyJfZmlsZV8iKyJfIikgKS5yZWFkKCkKCmRlZiBkZWNvZGUocyk6CglyZXR1cm4gc3RyKGJhc2U2NC5iNjRkZWNvZGUocy5yZXBsYWNlKCJfIiwgc3RyKCkgKSApLCAidXRmLTgiICkKCmRlZiBlbmNvZGUocyk6CglzPXN0cihiYXNlNjQuYjY0ZW5jb2RlKGJ5dGVzKHMsICJ1dGYtOCIpICksInV0Zi04IikKCXJlc3VsdCA9IHN0cigpLmpvaW4oW2N1ciBpZiBwcmV2IT1jdXIgZWxzZSAiXyIrY3VyIGZvciBwcmV2LGN1ciBpbiB6aXAoW05vbmVdK2xpc3QocykscyldKQoJcmV0dXJuIHJlc3VsdAoKZGVmIGdldF90ZW1wbGF0ZSgpOgoJcmV0dXJuIGRlY29kZSgiQjY0X0VOQ09ERURfUEFSVFMiKQoKZGVmIGdldF93cmFwZXIoKToKCXJldHVybiBkZWNvZGUoIldSQVAiKQoKZGVmIGdldF9wYXlsb2FkKCk6Cgl3cmFwZXIsIHRlbXBsYXRlID0gZ2V0X3dyYXBlcigpLCBnZXRfdGVtcGxhdGUoKQoJZW5fdGVtcGxhdGVfNjQgPSBlbmNvZGUodGVtcGxhdGUpCgl3cmFwZXJfNjQgPSBlbmNvZGUod3JhcGVyKQoJcmV0dXJuIHRlbXBsYXRlLnJlcGxhY2UoIkI2NF9FTkMiKyJPREVEX1B_BUlRTIiwgZW5fdGVtcGxhdGVfNjQpLnJlcGxhY2UoIldSIisiQVAiLCB3cmFwZXJfNjQpCgpkZWYgZ2V0X29yaWdpbmFsKCk6CglwYXlsb2FkID0gZ2V0X3BheWxvYWQoKQoJd3JhcGVyID0gZ2V0X3dyYXBlcigpCglyZXR1cm4gd3JhcGVyLnJlcGxhY2UoIjEyMyIsIHJlcHIocGF5bG9hZClbMTotMV0gKQoKZGVmIG1haW4ocyk6Cglmb3IgaSwgKGEsIGIpIGluIGVudW1lcmF0ZSg_gemlwKGdldF9vcmlnaW5hbCgpLCBhY3R1YWxfc291cmNlKCkgKSApOgoJCWlmIGEgIT0gYjoKCQkJcmV0dXJuIHNbOmldICsgc1tpICsgMTpdCglyZXR1cm4gcwoKaWYgbm90IChldmFsKCJfIisiX25hbWVfIisiXyIpICE9ICJfIisiX21haW5fIisiXyIpOgoJcHJpbnQobWFpbihpbnB1dCgpICkgKQ=_=")\n\ndef get_wraper():\n\treturn decode("ZUV6ZkZ6ZUV6PWFBemNDemFBej1hQXpiQno9YkJ6YUF6PWFBemNDej1lRXpmRno9ZkZ6YUF6PWVFemFBej1kRHphQXo9ZXhlPWV4Yz14ZWM9eGVjZXhlYz1hQXo9YUE9YXo9QXo9YUF6PWZyPSciMSIjXG4xMjMnIycjJwoxMjsKYkJ6PWJCPWJ6PUJ6PWJCej1mcj0nIjEiI1xuMTIzJyMnIycKMTI7CmNDej1jQz1jej1Dej1jQ3o9ZnI9JyIxIiNcbjEyMycjJyMnCjEyOwpleGU9ZXhjPXhlYz1leGVjCmREej1kRD1kej1Eej1kRHo9YUF6LGJCeixhQXo7CmVFemZGemVFej1nR3plRXo9YUF6Y0N6PWFBejxjQ3o7CmVFej1lRT1lej1Fej1lRXo9YUF6PGNDejsKZkZ6PWZGPWZ6PUZ6PWZGej1hQXo+Y0N6OwplRXpmRno9ZUV6fGZGejsKZ0d6PWdHPWd6PUd6PWdHej1lRXp8ZkZ6OwpleGVjKGREeltnR3pdKQ=_=")\n\ndef get_payload():\n\twraper, template = get_wraper(), get_template()\n\ten_template_64 = encode(template)\n\twraper_64 = encode(wraper)\n\treturn template.replace("B64_ENC"+"ODED_PARTS", en_template_64).replace("WR"+"AP", wraper_64)\n\ndef get_original():\n\tpayload = get_payload()\n\twraper = get_wraper()\n\treturn wraper.replace("123", repr(payload)[1:-1] )\n\ndef main(s):\n\tfor i, (a, b) in enumerate( zip(get_original(), actual_source() ) ):\n\t\tif a != b:\n\t\t\treturn s[:i] + s[i + 1:]\n\treturn s\n\nif not (eval("_"+"_name_"+"_") != "_"+"_main_"+"_"):\n\tprint(main(input() ) )'#'#'
12;
cCz=cC=cz=Cz=cCz=fr='"1"#\nimport base64\n\ndef actual_source():\n\treturn open(eval("_"+"_file_"+"_") ).read()\n\ndef decode(s):\n\treturn str(base64.b64decode(s.replace("_", str() ) ), "utf-8" )\n\ndef encode(s):\n\ts=str(base64.b64encode(bytes(s, "utf-8") ),"utf-8")\n\tresult = str().join([cur if prev!=cur else "_"+cur for prev,cur in zip([None]+list(s),s)])\n\treturn result\n\ndef get_template():\n\treturn decode("aW1wb3J0IGJhc2U2NAoKZGVmIGFjdHVhbF9zb3VyY2UoKToKCXJldHVybiBvcGVuKGV2YWwoIl8iKyJfZmlsZV8iKyJfIikgKS5yZWFkKCkKCmRlZiBkZWNvZGUocyk6CglyZXR1cm4gc3RyKGJhc2U2NC5iNjRkZWNvZGUocy5yZXBsYWNlKCJfIiwgc3RyKCkgKSApLCAidXRmLTgiICkKCmRlZiBlbmNvZGUocyk6CglzPXN0cihiYXNlNjQuYjY0ZW5jb2RlKGJ5dGVzKHMsICJ1dGYtOCIpICksInV0Zi04IikKCXJlc3VsdCA9IHN0cigpLmpvaW4oW2N1ciBpZiBwcmV2IT1jdXIgZWxzZSAiXyIrY3VyIGZvciBwcmV2LGN1ciBpbiB6aXAoW05vbmVdK2xpc3QocykscyldKQoJcmV0dXJuIHJlc3VsdAoKZGVmIGdldF90ZW1wbGF0ZSgpOgoJcmV0dXJuIGRlY29kZSgiQjY0X0VOQ09ERURfUEFSVFMiKQoKZGVmIGdldF93cmFwZXIoKToKCXJldHVybiBkZWNvZGUoIldSQVAiKQoKZGVmIGdldF9wYXlsb2FkKCk6Cgl3cmFwZXIsIHRlbXBsYXRlID0gZ2V0X3dyYXBlcigpLCBnZXRfdGVtcGxhdGUoKQoJZW5fdGVtcGxhdGVfNjQgPSBlbmNvZGUodGVtcGxhdGUpCgl3cmFwZXJfNjQgPSBlbmNvZGUod3JhcGVyKQoJcmV0dXJuIHRlbXBsYXRlLnJlcGxhY2UoIkI2NF9FTkMiKyJPREVEX1B_BUlRTIiwgZW5fdGVtcGxhdGVfNjQpLnJlcGxhY2UoIldSIisiQVAiLCB3cmFwZXJfNjQpCgpkZWYgZ2V0X29yaWdpbmFsKCk6CglwYXlsb2FkID0gZ2V0X3BheWxvYWQoKQoJd3JhcGVyID0gZ2V0X3dyYXBlcigpCglyZXR1cm4gd3JhcGVyLnJlcGxhY2UoIjEyMyIsIHJlcHIocGF5bG9hZClbMTotMV0gKQoKZGVmIG1haW4ocyk6Cglmb3IgaSwgKGEsIGIpIGluIGVudW1lcmF0ZSg_gemlwKGdldF9vcmlnaW5hbCgpLCBhY3R1YWxfc291cmNlKCkgKSApOgoJCWlmIGEgIT0gYjoKCQkJcmV0dXJuIHNbOmldICsgc1tpICsgMTpdCglyZXR1cm4gcwoKaWYgbm90IChldmFsKCJfIisiX25hbWVfIisiXyIpICE9ICJfIisiX21haW5fIisiXyIpOgoJcHJpbnQobWFpbihpbnB1dCgpICkgKQ=_=")\n\ndef get_wraper():\n\treturn decode("ZUV6ZkZ6ZUV6PWFBemNDemFBej1hQXpiQno9YkJ6YUF6PWFBemNDej1lRXpmRno9ZkZ6YUF6PWVFemFBej1kRHphQXo9ZXhlPWV4Yz14ZWM9eGVjZXhlYz1hQXo9YUE9YXo9QXo9YUF6PWZyPSciMSIjXG4xMjMnIycjJwoxMjsKYkJ6PWJCPWJ6PUJ6PWJCej1mcj0nIjEiI1xuMTIzJyMnIycKMTI7CmNDej1jQz1jej1Dej1jQ3o9ZnI9JyIxIiNcbjEyMycjJyMnCjEyOwpleGU9ZXhjPXhlYz1leGVjCmREej1kRD1kej1Eej1kRHo9YUF6LGJCeixhQXo7CmVFemZGemVFej1nR3plRXo9YUF6Y0N6PWFBejxjQ3o7CmVFej1lRT1lej1Fej1lRXo9YUF6PGNDejsKZkZ6PWZGPWZ6PUZ6PWZGej1hQXo+Y0N6OwplRXpmRno9ZUV6fGZGejsKZ0d6PWdHPWd6PUd6PWdHej1lRXp8ZkZ6OwpleGVjKGREeltnR3pdKQ=_=")\n\ndef get_payload():\n\twraper, template = get_wraper(), get_template()\n\ten_template_64 = encode(template)\n\twraper_64 = encode(wraper)\n\treturn template.replace("B64_ENC"+"ODED_PARTS", en_template_64).replace("WR"+"AP", wraper_64)\n\ndef get_original():\n\tpayload = get_payload()\n\twraper = get_wraper()\n\treturn wraper.replace("123", repr(payload)[1:-1] )\n\ndef main(s):\n\tfor i, (a, b) in enumerate( zip(get_original(), actual_source() ) ):\n\t\tif a != b:\n\t\t\treturn s[:i] + s[i + 1:]\n\treturn s\n\nif not (eval("_"+"_name_"+"_") != "_"+"_main_"+"_"):\n\tprint(main(input() ) )'#'#'
12;
exe=exc=xec=exec
dDz=dD=dz=Dz=dDz=aAz,bBz,aAz;
eEzfFzeEz=gGzeEz=aAzcCz=aAz<cCz;
eEz=eE=ez=Ez=eEz=aAz<cCz;
fFz=fF=fz=Fz=fFz=aAz>cCz;
eEzfFz=eEz|fFz;
gGz=gG=gz=Gz=gGz=eEz|fFz;
exec(dDz[gGz])

This program has the general structure
a="payload"
b="payload"
c="payload"
exec([a,b][b==c])

where "payload" is the program we want to execute. The comparison in the exec call makes sure that we run an unmodified copy of the payload.
Within the payload, we open the source of __file__ and compare it to the unmodified source (which is determined using a quine). This gives us the information we need to delete the correct character from the output.
The base64 encoded segments are the inputs used when making the quine. They are base64 encoded to avoid needing to escape nested quotes. The program is written so that it has no repeating characters, allowing almost any deletion to be detected.
The 6 characters that can't be deleted are:

The parentheses and brackets in the last line (([])), as deleting them will cause a syntax error.
The x in both instances of exec, as this produces the variable eec which is not defined.

If I did define eec, I would introduce a pair of duplicated characters, and would not be able to distinguish between those deletions. If the challenge cared about deletions of characters instead of bytes then I could define eᵉc in the appropriate places to reduce the score to 4.
Radiation Resistance tricks
eEzfFzeEz=aAzcCzaAz=... This line defines all the variables that the program could later try to read, to avoid crashing with a NameError.
bBz=bB=bz=Bz=bBz= when I want to be able to read a value, I define it (bBz) twice, and also define the mangled versions of it once each. When the variable is later accessed, at least one of the original definitions was undamaged, and damaging the later access will result in reading one of the shortened versions of the variable. No variable shorter than 2 characters is used as deleting a single variable would cause
bBz=fr='"1"#\n This acts as a no-op when the payload is executed, and results in any radiation to the start of the string merely storing a bad value in bBz.

If the single quote is deleted, "1" is assigned, and the rest of the string is commented out.
If the last = is deleted, the string becomes a raw format string, but is still valid syntax.
Other deletions mangle some variable names, but redundant assignment fixes this.

At the end of the strings '#'#' is used. As written this is the end of one string followed by a comment. If the first ' is removed, it the next #' become part of the string, and the remaining 2 characters become a comment. If the first # is removed, then the program has 2 strings in a row, so they are concatenated. Damage to the last 2 characters is just a change to the comment text.
12; and ; are added to allow the deletion of newlines to join statements without issue.
dDz=aAz,bBz,aAz; This line stores the A and B in a tuple. If either comma is deleted, we still store a tuple of size two. The 3rd tuple element will never be accessed. Deleting the = has no effect, as dDzaAz was initialized to the same value as aAz.
a != c is converted to e = a<c; f = a>c; g = e|f so that characters in the comparison operator can be deleted without crashing the program. Then redundant comparisons are added so that damaging any one of the comparisons produces the right result.
exec(dDz[gGz]) we can execute a known-good payload by indexing into a tuple.

Python 3, 33 - 1 = 32  bytes
The removable byte is the 23rd one (~).
x=input();print(x[:22-~0]+x[23:])

Try it online!
This sort of evolved out of Arnauld's answer, but only seeks to get one removable character.
Explanation
In python ~ is a "bitwise not", which basically subtracts the value from 1.  So ~0 is equivalent to -1.  This makes 22-~0 the same as 23. So normally our program takes everything up to the 23rd character and then adds everything after.  However when we remove the ~ 22-~0 becomes 22-0 equivalent to 22.  This makes us take the first 22 characters and everything after the 23rd, skipping the 23rd character.

Shakespeare Programming Language, 236 bytes, score 235
-16 bytes thanks to Jo King.
New lines added for readability. Byte 196 can be removed. Exits with error but the output is correct.
,.Ajax,.Page,.
Act I:.Scene I:.
[Exeunt][Enter Ajax and Page]
Ajax:Open mind.
Page:You be the sum ofyou a cat.
     Be you as big as the square ofthe sum ofa big big big big cat a big pig?
     If soLet usScene VI.

Scene VI:.
Ajax:Speak mind.Let usAct I.

Try it online!
Page reads bytes from input and outputs them, one at a time. Ajax is a counter indicating how many bytes have been read. Page's if statement tests Ajax == 196. In the basic version, the if statement is useless: in all cases, we proceed to Scene VI. If byte 196 has been removed, the statement becomes If soLet usScene I. so we skip Scene VI and the corresponding input does not get output.
This uses the fact that SPL doesn't require scenes to use consecutive numbers.
Looking for other strategies, I checked all the words allowed in SPL: the only useful pair of words which are one deletion apart is Helen/Helena, which are both legal characters. I might try to use this to get a solution where two different bytes can be removed.

Charcoal, 6 bytes, score 5
ΦＳ⁻κ±⁴

Try it online! Explanation:
 Ｓ      Input string
Φ       Filtered
   κ    Current index
  ⁻     Subtract
     ⁴  Literal `4`
    ±   Negated

Since adding 4 to the index is always a truthy result, this is a cat program. Removing the ± at index 4 results in the following program:
ΦＳ⁻κ⁴

Try it online! It deletes the character at index 4 (0-indexed).

C (gcc), 54 53 bytes, score 52 51
Bytes 27 or 28 (from the first ++c), or both, may be removed.
Explanation:
There is a counter that increments by 2 each iteration (one in the loop condition, one in the body) in the unmodified program, but removing one of the +s turns the first increment into a no-op. As the only way to get an odd number is in the modified program, only it will remove the 27th byte. Both bytes can be removed because +c is the same as c.
c;main(a){for(;a=~getchar(++c);)++c-27&&putchar(~a);}

Try it online!

GNU Sed 7 bytes, score=6
Full program
b;s/.//
Program with first byte removed
;s/.//
Input is a  printable ASCII string, with no newlines.

Strange that s/.^// interprets ^ as non-special whereas s/^// doesn't

JavaScript, 52 bytes, score 51
The 37th byte, 1, is removable.

s=prompt()
console.log(s.slice(0,36+01)+s.slice(37))

JavaScript, 56 bytes, score 55
The 24th byte, 1, is removable.

s=prompt()
console.log(01?s:s.replace(/^(.{24})./,'$1'))

For the first one, 1+33 saves a byte, as you can remove the 1 to leave +33.

Symbolic Python, 11 bytes, score 10
__=_[_==_:]

Try it online!
The first _ is removable. Basically, the program sets __ to the input without the first character, which doesn't affect the IO variable _, so it outputs what is inputted. If you remove the _. it does set the variable, so the first character is removed. Try it online!

J, 14 - 1 = 13 bytes
1(]}.~1-1-:[)]

Try it online!
Removing the first byte works.
how

1(...)] A fork with the constant 1 as the left tine and the input as the right ].
(]}.~1-1-:[) One minus 1- the value "does the input equal the left fork tine?" 1-:[.

This will always be 0 in the unaltered program.
It will always be 1 in the program-with-byte1-removed.  No string matches the integer 1.
This works because removing that first 1 turns the fork into a hook, which is like a fork whose left tine is the original input.

]}.~ Remove from the beginning }.~ of the original input ] that number of characters -- ie, the 1 or 0 calculated in the previous step.

Vyxal, score 1
,Ḣ

Try it Online!
,  # Print
 Ḣ # Remove first character, ignored

Ḣ  # Remove first character, implicitly printed

Pyth, 2 bytes - Score: 1
&t

Try it online!
This implicitly expands to:
imp_print((tail(Q) and Q))
Removing the '&' let's the tail function print (All but the first element)

Python 3, score = 27, 262 bytes
x="a,b=sorted(x,key=len);d=[3,123][x!=(a,b)];n=sum(i is j for i,j in zip(a,b) );s=input();print(s[:d+n]+s[d+n+(a!=b):]) ","a,b=sorted(x,key=len);d=[3,123][x!=(a,b)];n=sum(i is j for i,j in zip(a,b) );s=input();print(s[:d+n]+s[d+n+(a!=b):]) "
exec(max(x,key=len))

Try it online!
redundancy is good


text:
Check if simple regex matches string
A simple regex is either:

_ (which matches the empty string)
Any lowercase letter a to z (which matches that letter)
r*, where r is a regex (which matches r any number of times)
(r|s), where r and s are regexes (which matches either r or s)
(r+s), where r and s are regexes (which matches r followed by s)

Note that due to the recursive definition, * can occur multiple times in a row.
Here are some examples of regexes and their matches:

(a+b) matches only ab
((a|_)+b*) matches , a, b, ab, bb, abb, but not baaa, aab
(c+(o+((l+(o+(u|_)))+r))) matches only color and colour
(a|b)* matches only strings containing letters a and b (so , ab, bab, but not abc)
(_***|(a+b***)) matches only the empty string or a followed by any number of bs.

Your task is to write a program that takes such a regex and a string of lowercase letters, and outputs whether or not the regex matches the entire string (output should be as described here).
The shortest code in bytes wins.

Welcome to the site!  You have tagged this as code-golf but there is no indication of the scoring criterion in the challenge.  Is code-golf with bytes the intended criterion?

Thanks! Yes, the shortest code wins. Added that to the question.

Additionally output formats for decision problems can vary.  Here is a meta post that goes into the options you as a challenge writer can choose.

Can we assume that the regex consists entirely of lowercase letters and _*(|)?

Yes, you can assume that it's a string of lower case letters, and that the input regex will be a valid regex as described above.

@Stefan Valid sure, but nowhere do you state that there won't be any other characters in the regex.

There won't be any other characters, because any regex must be one of those five cases above (and nothing else), which are composed of only characters ()|+* and a to z. I'll add some examples of cases that don't match.

Suggested rewording of entire post: Given a [a-z]* string and a [a-z_()|+*]+ PCRE regex, output whether the entirety of the string would be matched by the regex after all _s and +s are removed from the regex. Strictly speaking, this isn't the same as it allows many more regexes, but most answers will probably use this method anyway.

What is the criterion that defines what regex rule to use, I mean that since it's a code-golf I guess most answers will use the simplest and shortest form of a possible regex, is it allowed?

@game0ver There's never any ambiguity: A sub-regex is either a single character or a parenthesis, optionally followed by an asterisk.

@Adám: That rewrite might make an implementation in a language without a built-in regex library even less likely if each "atom" is a string of letters instead of just a single character.  It's a good summary of one valid implementation strategy, though.  Although I was kind of hoping this challenge wouldn't be so easily solved just by translating this into standard regex so more answers would have to implement a simple regex engine, maybe with backtracking via recursion for example.

I don't think (c+(o+(l+((o+u)|o)+r))) strictly matches the grammar. (l+((o+u)|o)+r) requires defining the associativity of + to have a well defined parse.

Hi Stefan. If you want to sharpen your regex skills in a fun way try "www.regexcrossword.com". It has tutorials that gently introduce you to using regex, getting progressively harder. After that it has a "user generated" regex crossword level which range from being difficult to utterly insanely devious. But going through the tutorials is enough for everyday regex usage.

@Robin Please note that the OP is not asking for any help here. :) Questions on Code Golf SE are just meant to be recreational programming challenges.

@Arnauld: Yup I understand, I thought it was a tad off topic, I have nothing to do with that web site other than enjoying playing it. A regex crossword is a very close relation to a programming challenge though :)!

@ankh-morpork has pointed out that under the question's definition of a simple regex, any number of * in a row results in a valid simple regex; for example, qa***z should match qz and qaaz. Is this the definition we should stick to? If so, 8+ answers will need to be edited, as they are currently incorrect.

In addition to Deadcode's comment above, _* is also a valid "simple regex" that matches only an empty string. Deleting _ would leave single *, which is a grammar error in most regex flavors (if not all).

Haskell, 203 bytes
Nobody had done this by implementing a small regex engine yet, and I felt like it had to be done. This obviously won't win. but I'm hoping it will inspire someone to write an even more golfed regex engine.
I've rewritten my solution to avoid directly parsing the regular expression into its AST. Instead, the parsing process constructs a function that is used to match a string against the input regex.
The main function is (&) :: String -> String -> Bool which takes a string representation of a regex and a string to test, returning a boolean value. This calls to the next function which handles most of the work in parsing the regex and matching the string.
Function p :: String -> ([String] -> [String], String) takes a string representation of a regex and returns as the first element of a tuple a function that returns a list of all possible unmatched suffixes of strings in the input list after satisfying the regex parsed from the input string. The regex fully matches the string if the empty string is contained in the list of possible unmatched suffixes.
r&s=elem""$fst(p r)[s]
p(c:t)|c>'`'=t% \s->[t|h:t<-s,c==h]|c>'^'=t%id|(l,o:t)<-p t,(r,_:u)<-p t=u%last(r.l:[\s->r s++l s|o>'+'])
m#s=s++filter(`notElem`s)(m s)
('*':t)%m=t%until(\s->s==m#s)(m#)
s%m=(m,s)

Try it online!
To get rid of one byte, I replaced import Data.List; m#s=nub$s++m s with m#s=s++filter(`notElem`s)(m s). These functions aren't equivalent if there are duplicate elements in either s of m s. The new function does, however, remove all elements from m s that already exist in s, so until still terminates once no new suffixes are discovered by the application of m.
Ungolfed Code
import Data.List

match :: String -> String -> Bool
match r s =elem ""$(fst $ parseRegex r)[s]

parseRegex :: String -> ([String] -> [String], String)
parseRegex ('_':t) = parseKleene id t
parseRegex (c:t) | c >= 'a' = parseKleene (>>=p) t
  where p (c':t')| c==c' = [t']
        p _ = []
parseRegex ('(':t) =
  let (l, (o:t')) = parseRegex t in
  let (r, (_:t'')) = parseRegex t' in
  parseKleene (if o=='+' then (r.l) else (\ss-> (r ss)++(l ss))) t''

parseKleene :: ([String] -> [String]) -> String -> ([String] -> [String], String)
parseKleene p ('*':t) = parseKleene p' t
  where
    p' ss
      | ss' <- nub$ p ss,
        ss /= ss' = ss ++ (p' ss')
      | otherwise = ss
parseKleene p s = (p,s)

GolfScript, 198 bytes
I was able to beat my Haskell solution by implementing the first algorithm I tried in GolfScript instead of Haskell. I don't think it's interesting enough for a separate answer, so I'll just leave it here. There are likely some golfing opportunities since I learned GolfScript just for this.
This solution is in the form of a block that expects the test string on the top of the stack followed by the regex string.
{[.;]\1+{(.96>{0[\]}{2%0{r\(\r\:s;[@]s(;\}i}{if}:i~\{(.3%}{;\2[\]\}until[.;]\+\}:r~\;{.{(.{.4%{2%{)@\m\)\;m}{)\;{.@.@m 1$|.@={\;}{\o}i}:o~}i}{;)@.@m@@\)\;m|}i}{;(:c;;{,},{(\;c=},{(;}%}i}{;}i}:m~""?}

Try it online!

Fantastic work and great golfing! It even works with ((((a|d)|(g|j))|((((c|f)|i)+((a|d)|(g|j))*)+((b|e)|h)))|(((((b|e)|h)|((((c|f)|i)+((a|d)|(g|j))*)+((c|f)|i)))+(((a|d)|(g|j))|((((b|e)|h)+((a|d)|(g|j))*)+((c|f)|i)))*)+(((c|f)|i)|((((b|e)|h)+((a|d)|(g|j))*)+((b|e)|h)))))*, the regex that tests decimal numbers for divisibility by 3 (where 0-9 is replaced with a-j).

@Deadcode thanks for bringing some attention to my solution. It's good to know that people are seeing it since I put a good bit of time into trying out different golfing strategies.

Incidentally I was wondering, why is _***** included as a test case, given that this is not a valid pattern as described by the problem? Not a big deal, just curious. My guess is that it's meant to show a pattern that works despite being invalid. (I notice if _ is replaced with a letter, it hangs.) If so, perhaps add a comment to that test case. Alternative guess: Something added during debugging/testing that was meant to be removed later.

@Deadcode The way I understand the problem, that pattern should be valid: _ is defined as a regex and r* is defined to be a regex for all regex r, so we can chain * as much as we like on top of _. With regards to hanging, I think (but am not confident) that it will terminate but in an embarrassingly exponential amount of time.

Very interesting point. Under this interpretation, every other answer would currently be incorrect.

@Deadcode It looks like you were correct that my code doesn't always terminate for chained stars on characters (some cases work due to lazy evaluation). There's an easy fix for 16 bytes although I may be able to do better. I might also just leave it as is given that such inputs aren't handled by other answers.

I do think you should fix it. I'm trying to get everybody else to fix theirs. Also, it appears it's not just chained stars that hang your code. What about ((a|_)*|_)* or (_+(_+a*)*)* attempting to match a?

@Deadcode The easy fix required importing Data.List for nub, but I'm fairly confident the problem is solved.

You can change %(\s->...) to % \s->...

APL (Dyalog Unicode), 39 bytesSBCS
Edit: now works with runs of * even after _
Full program. Prompts stdin for string and then for regex. Returns a list consisting of an empty list (by default, this prints as two spaces) for matches and an empty list (empty line) for non-matches.
(1⌽'$^','\*+' '_'⎕R'*' '()'⊢⍞~'+')⎕S⍬⊢⍞

Try it online! (output made easier to read by converting all output to JSON)
⍞ prompt stdin (for string)
⊢ on that, apply the following:
(…)⎕S⍬ PCRE Search for the following, returning an empty list for each match
 ~'+' remove all plusses from the following:
 ⍞ prompt stdin (for regex)
⊢ on that, apply the following:
 '\*+' '_'⎕R'*' '()' PCRE Replace runs of * with * and _ with ()
 '$^', prepend dollar sign and caret (indicating end and start)
 1⌽ rotate the first character ($) to the end

_*** is a valid simple regex. Please revise your answer to allow it.

@Deadcode Fixed.

Python 3, 69 bytes
lambda r,s:re.match(re.sub('(?<=[_*])\*+|[_+]','',r)+'$',s)
import re

Try it online!
Simple - just convert it to ordinary regex, using ordinary regex!

I think this is actually a 56 byte answer, because you don't have to count the f= for lambda answers, and can put the import after it: Try it online!

_*** is a valid simple regex. Please revise your answer to allow it.

@Deadcode it may be two years late... but I've done it.

APL (Dyalog Unicode), 295 277 bytes
a←819⌶⎕A
E←{⍵{(⍺⊆⍨~⍵),⍺[⍸⍵]}(⍵∊'|+')∧0=+\-⌿'()'∘.=⍵}1↓¯1↓⊢
M←{c←⊃⌽⍵⋄c∊'0',a:0⋄c∊'_*':1⋄r s o←E⍵⋄o='|':∨/∇¨r s⋄∧/∇¨r s}
D←{c←⊃⌽⍵⋄c∊'0_':'0'⋄c=⍺:'_'⋄c∊a:'0'⋄c='*':1⌽∊')('(⍺∇¯1↓⍵)'+'⍵⋄r s o←E⍵⋄o='|':1⌽∊')('(⍺∇r)'|',⍺∇s⋄M r:1⌽∊')(('(⍺∇r)'+'s')|',⍺∇s⋄1⌽∊')('(⍺∇r)'+'s}
{M⊃D/(⌽⍵),⊂⍺}

Try it online!
-18 bytes thanks to @ngn.
This is a proof of concept that we can do a "simple regex matching" without any backtracking, thus avoiding possible infinite loops due to _* or r**. This is also a showcase that APL is a general-purpose programming language.
The anonymous function at the last line does the regex matching; use it as (regex) f (input string). The return value is 1 if the match is successful, 0 otherwise.
Concept
Given a simple regex R and the first character c of input string, we can construct (or derive) another simple regex R' that matches exactly the strings s where the original R matches c+s.
$$
\forall R \in \text{simple regex}, c \in \text{[a-z]}, s \in \text{[a-z]*}, \\
\exists R' \in \text{simple regex}, R' =\sim s \iff R =\sim c+s
$$
Combine this with a tester which checks if r matches an empty string (epsilon), and we get a fully working simple regex matcher: given a regex \$ R_0 \$ and string \$ s = c_1 c_2 \cdots c_n \$, sequentially derive \$ R_0, c_1 \rightarrow R_1, c_2 \rightarrow R_2 \cdots \rightarrow R_n \$ and then test if \$ R_n \$ matches epsilon.
My code uses the following algorithm for testing epsilon match (MatchEps) and computing R' from R and c (Derive).
T = True, F = False
0 = null regex (never matches)
_ = "empty string" regex
a = single-char regex
r, s = any (sub-)regex

MatchEps :: regex -> bool
MatchEps 0 = F    # Null regex can't match empty string
MatchEps _ = T    # Empty-string regex trivially matches empty string
MatchEps a = F    # Single-char can't match
MatchEps r* = T   # Kleene matches as zero iteration
MatchEps (r|s) = MatchEps r or MatchEps s
MatchEps (r+s) = MatchEps r and MatchEps s

Derive :: char -> regex -> regex
# No matching string at all
Derive c 0 = 0
# _ can't match any string that starts with c
Derive c _ = 0
# Single-char regex only matches itself followed by empty string
Derive c a = if c == 'a' then _ else 0
# r* matches either _ or (r+r*);
# _ can't start with c, so it must be first `r` of (r+r*) that starts with c
Derive c r* = ([Derive c r]+r*)
# r or s; simply derive from r or derive from s
Derive c (r|s) = ([Derive c r]|[Derive c s])
# r followed by s; it matters if r can match _
Derive c (r+s) =
  # if r matches _, either [r starts with c] or [r matches _ and s starts with c]
  if MatchEps r then (([Derive c r]+s)|[Derive c s])
  # otherwise, r always starts with c
  else ([Derive c r]+s)

Ungolfed, with comments
⍝ Unwrap single layer of (...) and extract (r, s, op) from (r|s) or (r+s)
ExtractRS←{⍵{(⍺⊆⍨~⍵),⍺[⍸⍵]}(⍵∊'|+')∧0=+\-⌿'()'∘.=⍵}1↓¯1↓⊢
  ⍝ 1↓¯1↓⊢    Drop the outermost ()
  ⍝ {...}     Pass the result to the function as ⍵...
  ⍝   +\-⌿'()'∘.=⍵    Compute the layers of nested ()s
  ⍝   (⍵∊'|+')∧0=     Locate the operator (`|` or `+`) as bool vector
  ⍝   ⍵{...}          Pass to inner function again ⍵ as ⍺, above as ⍵
  ⍝     ⍺[⍸⍵]     Extract the operator
  ⍝     (⍺⊆⍨~⍵),  Prepend the left and right regexes

⍝ Tests if the given regex matches an empty string (epsilon, eps)
MatchEps←{
    c←⊃⌽⍵                 ⍝ Classify the regex by last char
    c∊'0',819⌶⎕A:0        ⍝ 0(no match) or lowercase: false
    c∊'_*':1              ⍝ _(empty) or Kleene: true
    r s op←ExtractRS ⍵    ⍝ The rest is (r|s) or (r+s); extract it
    op='|': ∨/∇¨r s       ⍝ (r|s): r =~ eps or s =~ eps
    ∧/∇¨r s               ⍝ (r+s): r =~ eps and s =~ eps
}

⍝ Derives regex `R'` from original regex `R` and first char `c`
Derive←{
    c←⊃⌽⍵             ⍝ Classify the regex by last char
    c∊'0_':,'0'       ⍝ 0 or _ doesn't start with any c
    c=⍺:,'_'          ⍝ Single char that matches
    c∊819⌶⎕A:'0'      ⍝ Single char that doesn't match
    c='*': '(',(⍺∇¯1↓⍵),'+',⍵,')'    ⍝ One char from Kleene: (R*)' = (R'+R*)
    r s op←ExtractRS ⍵               ⍝ Extract (r|s) or (r+s)
    op='|': '(',(⍺∇r),'|',(⍺∇s),')'  ⍝ (r|s): one char from either branch
    MatchEps r: '((',(⍺∇r),'+',s,')|',(⍺∇s),')'   ⍝ (r+s) and r =~ eps: ((r'+s)|s')
    '(',(⍺∇r),'+',s,')'                           ⍝ (r+s) but not r =~ eps: (r'+s)
}

⍝ Main function: Fold the string by Derive with initial regex,
⍝                and then test if the result matches eps
f←{MatchEps⊃Derive/(⌽⍵),⊂⍺}

Final note
This is not an original idea of mine; it is part of a series of exercises on a theorem proving textbook. I can claim that the algorithm is proven to work (because I did complete the correctness proofs), though I can't open the entire proof to public.

Very cool solution. I remember working through those exercises when I took a software verification course - Lots of fun.

JavaScript (ES6), 45 bytes
Takes input as (regex)(string). Returns a Boolean value.
Applies the straightforward method of removing [_+] from the simple regex to turn it into a standard regex.
r=>s=>!!s.match(`^${r.replace(/[_+]/g,"")}$`)

Try it online!
Or 43 bytes by returning either null or an object.

_*** is a valid simple regex. Please revise your answer to allow it.

Retina, 38 35 bytes
*1A`
1G`
^
a`
_
()
\*+
*
"$-5"~`\+

Try it online! Takes the simple regex on the first line and the string to match on the second. Explanation:
*1A`

Delete the first line, but don't actually change the working string. The string to match still gets stored in the history, which allows us to refer to it later.
1G`

Keep only the first line.
^
a`

Prefix the a modifier to anchor the pattern to the whole string.
_
()

Turn the _s into ()s to match an empty string that can be "repeated" with *.
\*+
*

Reduces runs of * to a single *.
\+

Delete any +s.
"$-5"~`

Execute that as a stage, using the history as the working string.

As specified in the problem, this is supposed to match the entire string (implied ^...$ around the pattern), but this is matching as if there is neither a ^ nor a $.

@Deadcode Sorry, I might not have noticed that edit by the time I finished answering the question.

Figured that might be it, which means you opened the question within within 27 minutes of when it was posted.

_*** is a valid simple regex. Please revise your answer to allow it.

@Deadcode It being the weekend, I probably had the "Newest Questions" tab open all day.

R, 55 75 bytes
function(x,y)grepl(paste0("^",gsub("([+_]|(?<=\\*))\\**","",x,pe=T),"$"),y)

Try it online!
A function that takes a simple regex x and a vector of strings y and returns a vector of logical values the same length as y indicating whether x matches.

_*** is a valid simple regex. Please revise your answer to allow it.

@Deadcode fixed to allow this and ab****.

Java 8, 55 bytes
r->s->s.matches(r.replaceAll("\\+|(_|(\\*))\\**","$2"))

Try it online.
Removes all +; all _ with zero or more trailing *; and changes all sequences of more than one subsequent * with a single *. Then it check if the String matches this modified regex. Note that in Java, the String#matches method implicitly adds a leading and trailing ^...$ to check the entire String.

_*** is a valid simple regex. Please revise your answer to allow it.

@Deadcode Both _*** and ab**** are fixed now at the cost of 15 bytes. Can probably be golfed a bit, though.

PHP, 983 976 954 930 910 892 838 bytes
<?php list(,$s,$i)=$argv;$p=0;$u=[2,[3,[2,[1,'('],$o=[2,[4,[2,&$u,[1,'|+']]],&$u],[1,')']],[1,'_'.join(range('a','z'))]],[5,[1,'*']]];m($o,$a);$s=$i;$p=0;echo m(o($a))&&$p==strlen($s);function m($m,&$a=[]){global$p,$s;$r=$p;$n=array_shift($m);foreach($m as$t){$b=[];if($n==1)if(($c=$s[$p]??0)&&strpos($t,$c)!==!1){$a[]=$c;$p++;return 1;}if($n==2){if(!m($t,$b)){$p=$r;return!1;}$a[]=$b;}if($n==3){if(m($t,$b)){$a[]=$b;return 1;}}if($n==4){k:$b=[];$r=$p;if(!m($t,$b)){$p=$r;return 1;}$a[]=$b;goto k;}if($n==5){if(m($t,$b))$a[]=$b;else{$a[]=[];$p=$r;}return 1;}if($n==6)return 1;}return $n==2?:$p!=$p=$r;}function o($a){$e=$b=u($a[1]);if($a[0]){$e=[2];foreach($a[0]as$u){$e[]=u($u[0]);$e[0]=$u[1][0]=='+'?2:3;}$e[]=$b;}return$e;}function u($u){$w=$u[0][0];$v=$w[0][0];$t=$v!='('?($v=='_'?[6,0]:[1,$v]):o($w[1]);return$u[1][0]==[]?$t:[4,$t];}

Try it online!
Ungolfed
<?php

list($dummy,$string,$user_test)=$argv;

$pointer = 0;

//production rules
$unit = [];
$char = ['char','_abcdefghijklmnopqrstuvwxyz'];
$separator = ['char','|+'];
$unit_and_separator = ['and',&$unit,$separator];
$operators_list = ['list',$unit_and_separator];
$operators = ['and',$operators_list,&$unit];
$open_bracket = ['char','('];
$close_bracket = ['char',')'];
$brackets = ['and',$open_bracket,$operators,$close_bracket];
$atom = ['or',$brackets,$char];
$star = ['opt',['char','*']];
$unit = ['and',$atom,$star];

$ast = [];
match($operators, $ast);

$user_regex = buildoperators($ast);

$user_ast = [];
$string = $user_test;
$pointer = 0;

// answer here 1=matched blank=not matched
echo match($user_regex, $user_ast)&&($pointer==strlen($string));

// recursive descent parser
function match($test_match, &$ast) {
    global $pointer,$string;

    $original_pointer = $pointer;

    foreach (array_slice($test_match,1) as $test) {
        switch ($test_match[0]) {
            case 'and':
                $sub_match = [];
                $pass = match($test,$sub_match);
                if (!$pass) {$pointer = $original_pointer;return false;}
                $ast[] = $sub_match;
                break;
            case 'or':
                $sub_match = [];
                $pass = match($test, $sub_match);
                if ($pass) {
                    $ast[] = $sub_match;
                    return true;
                }
                break;
            case 'list':
                do {
                    $sub_match = [];
                    $original_pointer=$pointer;
                    $pass = match($test, $sub_match);
                    if (!$pass) {
                        $pointer = $original_pointer;
                        return true;
                    }
                    $ast[] = $sub_match;
                } while (true);
                break;
            case 'char':
                $char = substr($string,$pointer,1);
                if ($char && @strpos($test,$char)!==false) {
                    $ast[]=substr($string,$pointer,1);
                    $pointer++;
                    return true;
                }
                break;
            case 'emptystring':
                return true;
                break;
            case 'opt':
                $pass = match($test, $sub_match);
                if ($pass) {$ast[] = $sub_match;} else {$ast[] = []; $pointer = $original_pointer;}
                return true;
                break;
        }
    }

    if ($test_match[0] == 'and') {
        return true;
    } else {
        $pointer = $original_pointer;
        return false;
    }
}

// build user production rules
function buildoperators($ast) {
    if ($ast[0]) {
        $engine = ['and'];
        foreach ($ast[0] as $unit_and_separator) {
            $engine[] = buildunit($unit_and_separator[0]);
            switch ($unit_and_separator[1][0]) {
                case '+':
                    $engine[0]='and';
                    break;
                case '|':
                    $engine[0]='or';
                    break;
            }

        }
        $engine[] = buildunit($ast[1]);
    } else {
        $engine = buildunit($ast[1]);
    }
    return $engine;
}

function buildunit($unit) {
    $star = !empty($unit[1][0]);
    if ($star) {
        return ['list',buildatom($unit[0][0])];
    } else {
        return buildatom($unit[0][0]);
    }
}

function buildatom($atom) {
    if ($atom[0][0]=='(') {
        return buildoperators($atom[1]);
    } elseif ($atom[0][0]=='_') {
        return ['emptystring',''];
    } else {
        return ['char',$atom[0][0]];
    }
}

Very cool that you did this, and welcome to PPCG! Seems it needs some bugfixes though. It is missing the implied $ at the end of the pattern, e.g. (a|b) matches ac. And it seems using * anywhere except after the outermost expression results in error messages, e.g. (a*|b) crashes, but (a|b)* works.

Also, (((a+a)|a)+(a+b)) should be able to backtrack and match aab, but currently only ((a|(a+a))+(a+b)) is matching aab.

Thanks @Deadcode. Both bugs are fixed in the golfed version. It's larger, not sure how much more I can squeeze from the lemon. The ungolfed version was working correctly with (a*|b). The backtracking is not compatible with my overly simplistic recursive descent parser. As you've noted treat it gently and check shorter productions first to make it work.

Just a few extras. It's worth noting that the original question did not specify the behaviour of something like a+b+c|d, since there are no precedence rules. My code will accept it, but treat it as a|b|c|d, which is probably undesirable. Also, my regex engine makes up for its deficiency in that it is fact more powerful than conventional regex, as it also returns an abstract syntax tree of the results (in $user_ast).

Perl 5, 47 + -alp flag = 50 bytes
$_=$F[0];s/[_+]/()/g;s/\*+/*/g;$_=$F[1]=~/^$_$/

Try it online!

Perl 5, 41 + -alp flag = 44 bytes
Obsolete: it not supports _***-like regexes
$_=eval'$F[1]=~/^'.($F[0]=~y/_+//rd).'$/'

Try it online!

_*** is a valid simple regex. Please revise your answer to allow it.

@Deadcode Fixed.

Perl 5, 34 + -p flag = 35 bytes
Full program. Takes the simple regex pattern, followed by string to match against, from stdin as two separate lines, then loops and does it again, until EOF is encountered. Prints 1 for a match or nothing for a non-match (with no newline in either case).
@ankh-morpork has pointed out that technically, given the question's description of simple regexes, any number of * in a row makes a valid simple regex. @Bubbler has pointed out that _* also needs to work (and be equivalent to _). The other answers haven't taken these things into account yet, but I will do so:
s/[_+]/()/g;s/\*+/*/g;$_=<>=~/^$_/

Try it online!
To allow simple regexes such as (_***+a) to work, _ is changed to () instead of . For golf reasons, + is also changed to (), although changing it to  would work.
This solution exploits the fact that valid input won't contain newlines, input can be assumed to be valid, and both the implicit <> (from -p) and the explicit <> include the terminating newline read from stdin, thus $ doesn't need to be added at the end of the regex (as both the pattern and the string ), only ^ needs to be inserted at the beginning.

Perl 5, 20 + -p flag = 21 bytes (looser, obsolete interpretation of question)
y/_+//d;$_=<>=~/^$_/

Try it online!
Like most of the other solutions, deletes the _ and + characters to turn the simple regex into a standard regex. This means that the simple regex (_*+a) will not work, as it becomes (*a) after the deletion. Anything containing ** won't work either; in standard regex, an already-quantified expression cannot be quantified again.

You don't need to count the flags into bytes. Instead, you can write e.g. Perl 5 `-p`, 34 bytes.

@Bubbler I know, but I've intentionally done this so that it is considered to be answer in the "Perl" language, and thus directly comparable to other answers in this language, rather than being in a different language, "Perl + -p".

C# (Visual C# Interactive Compiler), 522 bytes
a=>b=>{int y=0,e=a.Length,k=0,o,q;var z=new int[e];for(;k<e;k++)if(a[k]<41){for(o=q=1;o>0;q++)o+=a[q+k]<41?1:a[q+k]>41?0:-1;z[k+--q]=k+1;z[k]=k+q+2;}void t(string s,int j){for(;j<e;){var l=a[j++];var w=j<e&&a[j]==42;if(j>1&&a[j-2]<41)for(int r=j,d=0;r<z[j-2]-1;)if(a[r++]>123&z.Take(r).Skip(j-2).Count(x=>x>0)%2>0)t(s,r);if(l>96&l<124)do{if(w)t(s,j+1);if(s==""||s[0]!=l)return;s=s[1..];}while(w);if(l==42&&a[j-2]==41||l<41&z[j-1]<=e&&a[z[j-1]-1]==42)t(s,z[j-1]);j=l>123?a.IndexOf(')',j)+1:j;}y=s==""?1:y;}t(b,0);return y;}

Saved 6 bytes thanks to ceilingcat
Try it online!

Japt -¡, 13 bytes
è^+'$iVr"_|%+

Try it
true

_*** is a valid simple regex. Please revise your answer to allow it.

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

sort: votes


text:
Advent calendar
Let's build an advent Calendar for this Christmas!
Many Advent calendars for children take the form of a large rectangular card with numbered windows for each day of December starting from 1 ( although advent time begins on Advent Sunday, which is the fourth Sunday before Christmas day and often happens to be on November) and leading up to and including Christmas Eve (24) or in some cases Christmas day (25).
Windows are distributed across the calendar at a random position.
Every day children open the corresponding calendar window to reveal a small gift(usually a chocolate)
Task
The task is to display an advent calendar up to date following these rules:

Include Christmas day : 1 to 25
For days that have passed put an opened empty window [ ] ( no different characters allowed ), so if i run the same code the next day there must be a new window opened.
At the current day put an opened window with a gift in it [@] ( instead of @ you can use every printable character excep for space, [ ,  ]  and digits ).
For the remaining days put only the day number.
At Christmas all the windows will be open, the next day they are all closed ( a new calendar ).
Windows must open only on December, windows must stay closed on every month except for December.
It should work the same way the next year, and every year, that is for example on December 2099 Windows start opening.
Windows are laid down in random order on a 5 rows x 5 columns grid.
The grid has not to be accurate, just consider that every windows must  have at least one character space separating them on every side: left, top, right and bottom hence numbers must have left and right spaces of at least two characters.

Example
Today (15 December) :

  23   21   [ ]   18   [ ]

  [ ]   [ ]   [ ]   [ ]   16

  24   22   [ ]   17   [ ]

  [ ]   [ ]   [ ]   25   19

  [ ]   [ ]   [@]   [ ]   20

Tomorrow (16 December) :

  [ ]   17   25   [ ]   [ ]

  [ ]   19   [ ]   18   21

  [ ]   23   24   [ ]   [ ]

  22   [@]   [ ]   [ ]   [ ]

  [ ]   20   [ ]   [ ]   [ ]

On every month different than December

18 23 21 12 19
25 24 3 15 14
16 9 17 20 4
8 2 11 5 7
6 13 10 22 1

Live example
All code-golf rules apply.

Is it supposed to work as well for the next coming years or only for this year? (I suppose it's possible to golf a few bytes on some implementations by assuming a 2019 timestamp.)

So this was in the Sandbox for… 8 minutes?

@JonathanAllan Indeed, Danish advent calenders don't include the 25th.

@Arnauld yes it's reusable like most of the commercial calendars, so year doesn't matter

@Jonathan Allan yes and sorry .. I meant Christmas day.. Fixing

@Jonathan Allan yes you have to use system time, no input

@Adám sorry but Christmas is just around the corner, since there was no Christmas challenge I felt a bit sad, I used the sandbox to check if my script worked but as I expected it didn't. This is an exception, I'll continue to use the sandbox in the future.

There's still ten days until Christmas...

@Jo King I don't want to see only open windows, at this time we have a good open/closed ratio

Earlier this year, I saw an Advent calendar where the gift was a different bottle of wine, so it's not just for kids to enjoy the gifts!

@Adám Wow, with this challenge I learnt that there are advent calendars with 25 windows. Also in Austria and Germany (and pretty sure in all countries where Christmas is celebrated at the evening of the 24th of December), advent calenders have 24 windows only.

Could you provide more testcase about Jan. 1st, Dec. 1st, Dec. 25th, Dec. 26th? since I don't know what advent calendar is...

@rexkogitans actually the regular advent calendar has 24 days but there are many exceptions on the market with 25 days or 24 + a merry Christmas slot

@rexkogitans Though, to be fair, in later years, I've seen those that begin with the 25th — of November! One more week of shopping…

@tsh for this challenge, on every month except for December, it must print only days (no opened windows) although as pointed out before nothing stop you to open windows on every month. Gonna add test cases for clarity.

@Adám in fact the real advent time begins on Advent Sunday, which is the fourth Sunday before Christmas day. It often happens to be on November.

APL (Dyalog Extended), 57 bytesSBCS
Full program.
5 5⍴('[ ]'¨@{⍵∊⍳d}'[@]'¨@{⍵=d})⍣(</11 25≥m d←2↑1↓⎕TS)?⍨25

Try it online!
?⍨25 random selection without replacement of 25 out of 1…25
⍣(…) if:
 ⎕TS date in [Y,M,D,h,m,s,t] format
 1↓ drop one element; [M,D,h,m,s,t]
 2↑ take two elements; [M,D]
 m d← distribute to variables m=M and d=D (for month-of-year and day-of-month numbers)
 11 25≥ are [11,25] greater than or equal to these (gives [0,1] during advent, [1,0] or [1,1] before, and [0,0] after)
 </ if the first number is less than the second…
 (…) then apply the following function:
  @{…} at elements where the following condition is true:
  ⍵=d the elements are equal to d (today's day-of-month number)
  '[@]'¨ replace each with the constant "[@]"
  @{…} at elements where the following condition is true:
  ⍵∊⍳d the elements are members of 1…d (today's day-of-month number)
  '[ ]'¨ replace each with the constant "[ ]"
5 5⍴ reshape into a five-by-five matrix

I started learning APL but I'm still at early stages (chapter 10) , I suppose it checks for months as </11 25≥m , can I ask you why you used p in the input? Is it something like an anonymous function?

@AZTECCO p is just the name of the program. TIO's Input corresponds to APL's session (REPL), so p is there to run the program.

@AZTECCO I'm always happy to assist with your APL learning over in the APL Orchard.

@AZTECCO And regarding the checking for months, yes indeed. I've added a full explanation now. Let me know if you want any further explanation of the algorithm.

Python 2, 181 189 181 179 Bytes
Original Code
Since I can't comment on other answers yet, this is a slight improvement on ElPedro's previous answer. Try it online! I changed ElPedro's 5th line to save an additional 9 bytes.
i=__import__
t=i("datetime").date.today();n=t.day
c=[('['+(' ','@')[d==n]+']',`d`)[t.month<12or d>n]for d in i("random").sample(range(1,26),25)]
while c:print' '.join(c[:5]);c=c[5:]

Correction #1
Gained 6 bytes in order to fix an error where windows remain open after December 25th until the new year. Try it online!
See corrected version of line 3 below:
c=[('['+(' ','@')[d==n]+']',`d`)[t.month<12or n>25or d>n]for d in i("random").sample(range(1,26),25)]

Correction #2
As per AZTECCO's suggestions, I'm back at 181 bytes :) Try it online!
c=[('['+' @'[d==n]+']',`d`)[t.month-12|25-n|n-d<0]for d in i("random").sample(range(1,26),25)]

Final
With streamlined import statements (Thanks to Reinstate Monica)
import random,datetime as D
t=D.date.today();n=t.day
c=[('['+' @'[d==n]+']',`d`)[t.month-12|25-n|n-d<0]for d in random.sample(range(1,26),25)]
while c:print' '.join(c[:5]);c=c[5:]

Hi and welcome to the site! Thanks for crediting me with the original answer. There seems to be a small problem with dates after 25 Dec Try it online!. If you fix that you will certainly get an upvote from me :)

Nice catch! Thanks for pointing that out. The new version should work past Dec 25th now :)

Suggestion c=[('['+' @'[d==n]+']',d)

[t.month-12|25-n|n-d<0] saves 2 more (if one of the 3 sets some negative bits the expression becomes true)

You can get this even shorter in Python 3

import random,datetime as D saves 2 bytes over i=__import__

JavaScript (ES6),  158 ... 150  148 bytes
Saved 2 bytes thanks to @Deckerz
Note: Strictly speaking, this should be marked as ES9 (ECMAScript 2018), in which the output format of Date.prototype.toString has been standardized.
f=(k=25,m,d=Math.random()*25+1|0,t=(new Date+0).match(/c ([01].|2[0-5])|:/)[1])=>k?(m^(m|=1<<d)?(d^t?d<t?'[ ]':d:'[@]')+`
 `[--k%5&&1]:'')+f(k,m):''

Try it online!
How?
(new Date).toString() returns the current date in the following format:
DDD MMM dd yyyy hh:mm:ss TimeZoneString

For instance:
Sun Dec 15 2019 12:29:35 GMT+0000 (Coordinated Universal Time)

We apply the following regular expression to this string:
/c ([01].|2[0-5])|:/

It attempts to match a day of December less than \$26\$ (it's the only month whose 3-letter abbreviation ends in "c"). If not found, we force the first : separator to be matched instead, in order to prevent match() from returning null.
Therefore, the variable \$t\$ (for 'today') is set to either a valid day of December, or undefined if we're outside the relevant period:
t = (new Date + 0).match(/c ([01].|2[0-5])|:/)[1]

\$f\$ is a recursive function that generates a random day \$d\$ in \$[1-25]\$ at each iteration and draws the corresponding slot in the calendar. It keeps track of drawn days in the bitmask \$m\$.
f = (                     // f is a recursive function taking:
  k = 25,                 //   k = remaining number of days to draw
  m,                      //   m = bitmask of drawn days
  d = Math.random() * 25  //   d = random day in [1-25]
      + 1 | 0,            //
  t = ...                 //   t = current day or undefined (see above)
) =>                      //
  k ?                     // if k is not equal to 0:
    ( m ^ (m |= 1 << d) ? //   if m is modified by setting the d-th bit:
        ( d ^ t ?         //     if d is not equal to t:
            d < t ?       //       if t is defined and d is less than t:
              '[ ]'       //         draw an opened window
            :             //       else:
              d           //         draw a closed window
          :               //     else:
            '[@]'         //       draw today's gift
        ) +               //
        `\n `             //     decrement k; append a linefeed at the end of a row
        [--k % 5 && 1]    //     or a space otherwise
      :                   //   else:
        ''                //     we need to pick another day: leave k unchanged and
                          //     append nothing
    ) + f(k, m)           //   append the result of a recursive call
  :                       // else:
    ''                    //   done: stop recursion

does it check for months?

although I found your motivation convincing I'm glad you updated your answer. And it's a very valuable one!

@AZTECCO No worries. On second thought, I think it actually makes much more sense to test the month.

Saved you 2 bytes f=(a=25,b,c=0|25*Math.random()+1,d=(new Date+0).match(/c ([01].|2[0-5])|:/)[1])=>a?(b^(b|=1<<c)?(c^d?c<d?"[ ]":c:"[@]")+`  `[--a%5&&1]:"")+f(a,b):""

@Deckerz Good catch. Thanks!

Perl 6, 91 bytes
-1 bytes thanks to nwellnhof
.put for (("[@]"if Date.today~~/12.(0?@(^26))$/),"[ ]"xx+$0,+$0^..25)[*;*].pick(*).rotor(5)

Try it online!
Explanation:
.put for (     ...    )         # Print for each of
          ("[@]"if Date.today~~/12.(0?@(^26))$/), # [@] if today is within the Christmas period
          "[ ]"xx+$0,     # As many [ ]s as the current date
          +$0^..25        # All the number from today to the 25th
                      [*;*]                  # Flatten
                           .pick(*)          # Randomise
                                   .rotor(5) # And split into chunks of 5

I think you need xx+$0 if the regex doesn't match. But @(^26) saves a byte.

Python 2, 231 222 216 202 197 193 189 bytes
i=__import__
t=i("datetime").date.today()
y=t.day
d=[(((`x`,"[ ]")[x<y],"[@]")[x==y],`x`)[t.month<12or y>25]for x in i("random").sample(range(1,26),25)]
while d:print' '.join(d[:5]);d=d[5:]

Try it online!
-3 with many thanks to @AZTECCO
Examples below use an earlier version of the answer but the formula is the same.
Before December: Try it online!
December after 25th: Try it online!

This python trick : https://codegolf.stackexchange.com/a/187046/84844   helps you save a bit.. 190

05AB1E, 38 37 32 31 30 bytes
žeD₂‹žfÂ‹P'@ú€…[ÿ]Dg25Ÿ«¦.r5ô»

-6 bytes thanks to @Grimmy.
Try it online or try it online with own specified month / day.
Explanation:
že          # Push the current day
  D         # Duplicate it
   ₂‹       # Check if it's smaller than 26
     žf     # Push the current month
       Â    # Bifurcate it (short for Duplicate & Reverse copy)
        ‹   # And check if this is smaller than the month itself
            # (this will check 1-9 < 1-9 (falsey); 10 < 01 (falsey); 11 < 11 (falsey);
            #  and 12 < 21 (truthy) - so this will only be truthy for 12 / December)
         P  # Take the product of the day and both checks,
            # which result in either the day, or 0 if either check was falsey
'@         '# Push character "@"
  ú         # Pad it with the earlier number amount of spaces
   €        # Map over each of these characters:
    …[ÿ]    #  Push string "[ÿ]", where the `ÿ` is automatically filled with the character
Dg          # Duplicate the list, and pop and push its length
  25Ÿ       # Create a list in the range [length, 25]
     «      # And merge it to the earlier created list
¦           # Then remove the first item (either a "[ ]", or the "[@]" if the checks
            #                             were falsey)
 .r         # Randomly shuffle the list
   5ô       # Split the list into sublists of size 5
     »      # Join each inner list by spaces, and these strings by newlines
            # (after which the result is output implicitly)

32

@Grimmy I was about to save the edits of the 33 byte version.. And that one 32-byte version can be golfed further to 31 by using €…[ÿ]. :)

Alternative 31

12Q to Â‹ for -1.

@Grimmy Oh, very smart, thanks! :)

R, 113 103 bytes
x=as.POSIXlt(Sys.time())
a=sample(25)
if(x$mo>10&(y=x$md)<26){a[a<y]="[ ]";a[a==y]="[@]"}
write(a,"",5)

Try it online!
A full program that prints an advert calendar as specified.

Red, 135 bytes
d: now c: collect[repeat n 25[keep n]]if all[d/3 = 12
d/4 < 26][repeat n d/4[c/:n:"[ ]"]c/:n:"[@]"]random c
loop 5[print take/part c 5]

Try it online!

Before December
On Christmas
After Christmas

Jelly, 76 75 bytes
⁶o”@;Ø[jⱮ;@Jị@
“j⁶ṖgqS^OßƭẸ#pÆẸ¥aRŀçĠrṬỤ{½9?ȷ²Yḅ»ḲḢ;Ɱ$ŒV12;Ɱ25¤i+Ɱ25ẊÇs5K€Y

Try it online!
A full program that prints an advent calendar. Longer than most Jelly programs because it has to call __import__('datetime').datetime.today().month and __import__('datetime').datetime.today().day in Python.
If the date and time could be provided as an argument, it could be much shorter:
Jelly, 35 34 bytes
⁶o”@;Ø[jⱮ;@Jị@
12;Ɱ25¤i+Ɱ25ẊÇs5K€Y

Try it online!

How would the code look if given month and day as arguments?

@Adám I’ve added a version that does this.

Ruby, 112 bytes
x=[*1..25].shuffle;5.times{|c|puts x[c*5,5].map{|w|['[@]','[ ]',w][((d=Time.now).day-(d.month-12)*30)<=>w]}*" "}

Try it online!

C#, 267 bytes
using System;using System.Linq;class _{static void Main(){var n=DateTime.Now;var t=n.Month==12?n.Day:0;Console.WriteLine(string.Join(" ",Enumerable.Range(1,25).OrderBy(_=>new Random().Next(-9,9)).Select((d,i)=>(i%5==0?"\r\n":"")+(d<t?$"[ ]":d>t?$"{d}":"[@]"))));}}

.Net Fiddle

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/


text:
Hexagonify™ a String Block
Sandbox
What is Hexagonification™?
Hexagonification™ is a transformation that creates a hexagon with 3 copies of a rectangle block, each skewed to the right by 30 degrees and then rotated by 0, 120 and 240 degrees respectively, as shown in the following image. A triangle hole may appear in the middle, but that isn't a great deal.

Challenge
Write a program or function that receives a block of string as an input and outputs the hexagonified™ input. You may assume that the input consists of only ASCII characters and new lines, and each line of the input has been padded so that all lines are of equal length. Empty spaces should be filled with space characters.
The input and output formats are flexible. You may receive a single string, a list of lines or a 2D array as input, and output a single string or a list of lines. You may have leading/trailing spaces on each line and leading/trailing newlines, provided that the hexagon is properly formed. See Sample IO for how the blocks should be positioned.
Sample IO
Input:
3x3
squ
are

Output:
   3 x 3
  e s q u
 u r a r e
3 q a a s 3
 x s r q x
  3 e u 3

Input:
longer.....
rectangular
block......

Output:
   l o n g e r . . . . .
  . r e c t a n g u l a r
 r . b l o c k . . . . . .
. a .                 b r l
 . l .               l e o
  . u .             o c n
   . g .           c t g
    . n k         k a e
     r a c       . n r
      e t o     . g .
       g c l   . u .
        n e b . l .
         o r . a .
          l . r .

Input:
vb
el
ro
tc
ik
c2
ax
l8

Output:
        v b
       8 e l
      x l r o
     2 a   t c
    k c     i k
   c i       c 2
  o t         a x
 l r           l 8
b e l a c i t r e v
 v 8 x 2 k c o l b

Input:
1.line

Output:
 1 . l i n e
e           1
 n         .
  i       l
   l     i
    .   n
     1 e

Input:
1
.
l
i
n
e

Output:
      1
     e .
    n   l
   i     i
  l       n
 .         e
1 e n i l . 1

Scoring
Your score will be the area of the hexagonified™ source code (including the triangle hole). Assuming your code can just fit inside a rectangle of width \$w\$ and height \$h\$, the score will be:
$$3wh+\frac{(w-h)(w-h+1)}{2}$$
Winning Criteria
The submission with the lowest score in each language wins. No standard loopholes.

Isn't there a language, HexaGolf with a built-in for this?

@640KB I'm not sure about that, but if there is such language, feel free to use it ;)

...waits with baited breath for a Hexagony answer.

May we accept a list of lines? May we return a list of lines?

@JonathanAllan Yes of course. Edited to reflect this.

Charcoal, score 235 168
→ＷＳ⊞υιＦυＦ
«Ｐ↙ιＭ²←»Ｆ
Ｍ⊖⌈ＥυＬκ↙Ｆ
Ｆυ«Ｐ↖κ↗»Ｆ
Ｍ⊖⌈ＥυＬκ↖Ｆ
Ｆυ«Ｐ⪫κ ↘»

Try it online! Takes input as a newline-terminated list. Charcoal's problem here is that a newline is a command, so I can't just throw newlines in arbitrarily. Instead, I'm passing them as arguments to the For command, which loops over each character in the string, which here is just the one character, therefore not modifying the behaviour of the program (except to change the implicit loop variables). As such, these dummy For instructions are not included in the explanation. Explanation:
→

Another no-op, just to make the code exactly rectangular.
ＷＳ⊞υι

Input the list.
Ｆυ«Ｐ↙ιＭ²←»

Print the lower right diagonal side.
Ｍ⊖⌈ＥυＬκ↙

Move to the bottom.
Ｆυ«Ｐ↖κ↗»

Print the lower left diagonal side.
Ｍ⊖⌈ＥυＬκ↖

Move to the top left.
Ｆυ«Ｐ⪫κ ↘»

Print the top side.

Python 2, score 918
m=input()
d={};h=len(m)
w=len(m[0])
r=range
for i in r(h*w):
 j,i=divmod(i,h)
 d[(i,i+j*2)]=d[
(h+j,h+2*w-2*i-j
-2)]=d[(w-j+h+~i
,w+i-j+~h)]=m[i
][j]
y,x=zip(
*d.keys())
for i in r(min
(y),max(y)+1):
 print''.join(d
.get((i,j),' ')
for j in r(min
(x),max(x)+1))

Try it online!

Jelly, width × height = 9 × 9 9 × 6, score = 243 168
ṚZÑṚŻL}¡
J’0ẋ;"K€
ṚÇḟ€⁶ZUÇŻ
Z,ṖẈ+L0ẋ
Ṫ;Ɱ";⁶o/
U¹ƭṛĿ¥Ɱ4Ç

Try it online!
A set of links that takes a list of Jelly strings as its input and returns a list of Jelly strings.
An alternative 8 × 7 with score of 169:
ṚZÑṚŻL}¡
J’0ẋ;"K€
ṚÇḟ€⁶ZUÇ
Z,ṖẈ+L0ẋ
Ṫ;Ɱ";⁶o/
ṛĿ"JŻ€3¦
U3Ð¡ṚÇ5Ŀ

Try it online!

+1 (but I've also enquired regarding lines as IO)

@JonathanAllan modified taking advantage of laxer IO rules.

Jelly, Score (8 by  9  8) =  216  192
¹¹ẈṀ_$⁶ẋ
ŒdṙLC$UŻ
ÇżUṚżZṚ$
ÇṚUF€ḟ€0
0ẋṂ;r/_Ṃ
Z,$ẈÇṖ⁶ẋ
4ĿŒHj¥"Ç
ỴÇµÑżK€Y

A full-program which prints the result.
Try it online! Or see the test-suite.
How?
Starts at the Main (unnumbered) Link, which is the bottom line of code.
¹¹ẈṀ_$⁶ẋ - Link 1: the output lines without leading spaces, A
¹        - no-op -> A
 ¹       - no-op -> A
  Ẉ      - length of each (A)
     $   - last two links as a monad - f(Ẉ(A))
   Ṁ     -   maximum
    _    -   subtract (vectorises)
      ⁶  - literal space character
       ẋ - repeat (vectorises)

ŒdṙLC$UŻ - Link 2: lines
Œd       - anti-diagonals (e.g. ["abcd","1234"] -> ["d3","c2","b1","a4"])
     $   - last two links as a monad - f(lines)
   L     -   length
    C    -   complement (1 - that)
  ṙ      - rotate (anti-diagonals) left by (1 - length(lines))
              i.e. rotate right by (#lines - 1)
      U  - reverse each
       Ż - prepend a zero (allows us to start the join one further
                           down - removed in Link 4 with ḟ€0)

ÇżUṚżZṚ$ - Link 3: lines
Ç        - call the last Link (2) as a monad = Link_2(lines)
  U      - reverse each (lines)
 ż       - zip together
       $ - last two links as a monad - f(lines)
     Z   -   transpose (lines)
      Ṛ  -   reverse
    ż    - zip together

ÇṚUF€ḟ€0 - Link 4: lines
Ç        - call the last Link (3) as a monad = Link_3(lines)
 Ṛ       - reverse
  U      - reverse each
   F€    - flatten each
     ḟ€0 - remove zeros from each

0ẋṂ;r/_Ṃ - Link 5: [w,h]       e.g. [8,3]
0        - literal zero             0
  Ṃ      - minimum (of w and h)     3
 ẋ       - repeat                   [0,0,0]
     /   - reduce ([w,h]) by:
    r    -   inclusive range        [8,7,6,5,4,3]
   ;     - concatenate              [0,0,0,8,7,6,5,4,3]
       Ṃ - minimum (of w and h)     3
      _  - subtract                 [-3,-3,-3,5,4,3,2,1,0]

Z,$ẈÇṖ⁶ẋ - Link 6: lines
  $      - last two links as a monad:
Z        -   transpose
 ,       -   pair
   Ẉ     - length of each - i.e. [w=width(S), h=height(S)]
    Ç    - call the last Link (5) as a monad = f([w,h])
     Ṗ   - pop (remove rightmost)
      ⁶  - literal space character
       ẋ - repeat (vectorises) - Note: z ẋ -n = []

4ĿŒHj¥"Ç - Link 7: lines
4Ŀ       - call Link 4 as a monad = Link_4(lines)
       Ç - call the last Link (6) as a monad = Link_6(lines)
      "  - zip with - i.e. for i in [1..] f(left[i],right[i]):
     ¥   -   last two links as a dyad:
  ŒH     -     split (Link_4(lines)[i]) into two halves
    j    -     join with (Link_6(lines)[i])

ỴÇµÑżK€Y - Main Link: List of characters, S
Ỵ        - split (S) at newline characters
 Ç       - call the last Link (7) as a monad = Link_7(lines)
  µ      - start a new monadic chain - i.e. f(Link_7(lines))
   Ñ     - call the next Link (1) as a monad = Link_1(Link_7(lines))
     K€  - join each (line) with space characters
    ż    - zip these together
       Y - join with newline characters
         - implicit print

Nice. And unlike mine, almost no wasted space!

Was quite an effort. I imagine it's still beatable (in Jelly) too!

With some challenges, it seems everyone in a given language tends to convene on a single similar solution. I quite like the fact that ours are pretty different here.


text:
Challenge: Functioning Blackjack Game
This is kept fairly original:
Write a program that creates a standard blackjack game:

input "h" means hit or draw another card
input "s" means stand, or take what you have already
Rules:
cards values are as follows:
2-9 = face value of card
10, J, Q, K = 10
aces = 11 unless player busts,  then it's only a 1
if 2 or more aces are drawn the first is 11, and each one after is a 1
the bank needs 18-21, and has to draw another card until they are in this range
this game runs forever
Procedure:

The player gets 2 cards at the beginning, shown by "Player:" in the console
(Example: Player gets 2 and 5 at the beginning. Console output: "Player: 2, 5").
The cards can be shown in the console by their values
(For example: 2, 10, 1 | No need for: 2, K, A), but you can also use
the card-names instead. Your choice
The input by the player is "h" for "hit" and "s" for "stand". Also allowed is 1 and 0
The bank gets one card at the beginning, shown by "Bank:"
At the end you get the output "Win", "Lose", "Draw",
or "Blackjack", depending on the game

This game is a code-golf challenge.

Do we show the bank's other cards and, if so, when? 2. Do we need to pick from some number of shuffled decks in a uniformly random manner, and if so do we need to implement any logic around when these cards get shuffled, like casinos do or may we shuffle every hand? 3. Some people might prefer accepting other input (e.g. 1 for hit 0 for stand) as it'll cut down on boiler-plate code, I'd suggest allowing for this lax input (but it's up to you, of course). [Note: do update the post with any clarifications regarding these queries rather than just answering here in comments :)]

...in fact also, 2.a. May our program exit after one hand?

Pretty complex challenge, it could benefit of some time in Sandbox for Proposed Challenges.

I also recommend the sandbox. To get you started there, you need to explain what constitutes Win/Lose/Draw/Blackjack. and what exactly happens after the user answers.

Oh, and don't forget to state what hitting and standing means. Challenges should be self-contained, and not rely external resources.

I think this should be tagged as interactive.

Python 3, 574 \$\cdots\$ 338 337 bytes
from random import*
e,L,B,P=exit,"Lose","Bank:","Player:"
d=[*range(2,12)]*4+[10]*12
c=d.pop
def h(l,m):
 l+=[c()]
 if sum(l)>21and 11in l:l[l.index(11)]=1
 print(m,*l);return sum(l)
shuffle(d)
b=[]
t=h(b,B)
p=[c()]
u=h(p,P)
u>20<e("Blackjack")
while'h'==input():u=h(p,P)
u>21<e(L)
while t<18:t=h(b,B)
22>t>u<e(L)
u==t<e("Draw")
e("Win")

Try it online!
Saved 34 bytes thanks to @ReinstateMonica!!!

list(...) can be [*...]

And correct me if I'm wrong, but since you don't ever change the list except its order, can't you take out *4 and change [10]*12 to [10]*3?

@ReinstateMonica Thanks, that's a good one! :-) If we take out the *4 etc then we wouldn't be playing with a full deck! In fact only one suit. So, for instance, two aces could never be in play.

Ah right, right; my head was thinking with choice rather than shuffle.


text:
Compare Dowker Knotation
Dowker notation is a common way of representing mathematical knots.
Dowker notation can be derived from a knot diagram in the following way (based on the description from the wikipedium):

We will label each of the \$n\$ intersections with two numbers whose absolute value is on the range \$1, \dots 2n\$ (one odd one even). To do this choose an arbitrary starting point and direction on the knot and begin traversing from there.  At every intersection encountered label the intersection \$m\$ where \$m\$ is one more than the number of intersections already encountered (e.g. the first intersection is labeled 1, the second 2 etc.).  However, if \$m\$ is even and the strand being followed passes over instead label the intersection with \$-m\$.  We do this until we reach the starting point again, at which point every intersection should have two labels.

Now that each intersection is labeled we create a list of the even labels, sorted by their corresponding odd label (in ascending order).  You could also think of this as the order we traversed the intersections skipping every other intersection.

This list is our Dowker notation

Consider this example knot:

Taken with permission from wikipedia user Frentos
If we traverse the pairs as indicated in the diagram we get the following labels:
(1, 6) (3, −12) (5, 2) (7, 8) (9, −4) (11, −10)

This gives us a Dowker notation of
[6, -12, 2, 8, -4, -10]

Your task is to take two knots in Dowker notation and determine if they are isotopic (the same knot represented in different ways).
Two knots are isotopic if you can rearrange one into the other without crossing it through itself.
The Reidemeister moves can be used to determine whether two diagrams contain isotopic knots.
Input
Dowker notation is actually the name given to a couple of related ways of representing knots.  There are a couple of permissable modifications you can make to the format:

You may choose to represent integers as a tuple of a boolean and a positive integer, where the boolean's value represents sign of the original number and the positive integer its magnitude.
e.g.
-5 -> (True, 5)
14 -> (False, 14)

Since the values in Dowker notation are always even you can choose to have them all divided by 2.
If we use our example from earlier:
[6, −12, 2, 8, −4, −10]
=>
[3, -6, 1, 4, -2, -5]

You may make any combination of these modifications to your input format.  Of course your input format must be consistent.
Output
Your code should output one of two distinct consistent values.  One of these should always be output when the notations represent the same knot and the other should always be output when the notations represent different knots.
Scoring
This is code-golf answers will be scored in bytes with fewer bytes being better.
Test cases
The same
-- Reidemeister move I
[6, -12, 2, 8, -4, -10] [6, -8, 2, -4]
-- Reidemeister move II
[4, 6, 2] [8, 6, 10, -2, 4]
-- Mirror
[6, -12, 2, 8, -4, -10] [-6, 12, -2, -8, 4, 10]
-- Change of starting location
[6, -12, 2, 8, -4, -10] [4, -6, 10, -2, -8, 12]
-- Two trefoils
[4, 6, 2] [ 6, -8, 2, -4]

Different
-- Unknot and trefoil
[] [4, 6, 2]
-- Trefoil and figure 8
[4, 6, 2] [6, 8, 2, 4]

I'm not sure I actually see this being useful, but would one permissible modification to the input format be to encode sign in the ones bit? It just sort of struck me as a pleasant fusion of the two modifications given.

@UnrelatedString Sure.  I might add some more possible input formats as well.  It's the least I can do with a challenge like this.

I'm definitely not an expert, but from the Wikipedia page (see section "Uniqueness and counting") it looks like the Dowker–Thistlethwaite notations can sometimes be ambiguous, i.e. two non-isotopic knots can have the same Dowker sequence. Is this right?

JavaScript (ES2020), 736 730 720 bytes
-3 thanks to Kevin Cruijssen
-10 thanks to Neil
(t,e,l="slice",n=0)=>(f=(t,e=Object.assign([],Object.fromEntries(t[a="map"](((a,f)=>[2*f,a<0?-a:a,a>0])).flatMap((([a,f,p])=>[[a,[--f,p]],[f,[a,!p]]])))))=>n=(f[(e.map((a=>e.find((([a,f],p)=>a==p+1?e=[...e[l](0,p),...e[l](p+2)].map((([a,f])=>[a-2*(a>p),f])):p[f-2*(f>p)-2*(f>a),m])):0)))),e)]||(f[e]=t,f(t,[...e[l](1),...e[l](0,1)].map((([a,f])=>[(a||e.length)-1,f]))),f(t,e.map((([a,f])=>[a,!f]))),(m=[1,-1]).map((a=>m.map((m=>e.map((([e,l],n,[...o],s=o[n+1]?.[0])=>s+1&&l==o[n+1][1]&s+m===o[e+a]?.[0]&&f(t,o,[o[n],o[n+1],o[e],o[e+a],o[s],o[s+m]]=[[s+m,l],[e+a,l],[s,p=o[e+a][1]],[n+1,!l],[e,!p],[n,!l]]))))))),t))!=t|n)(t)|f(e)

input.value="# interactive; edit or add lines and the annotation will update\n\n# isotopic\n[6, -12, 2, 8, -4, -10] [6, -8, 2, -4]\n[4, 6, 2] [8, 6, 10, -2, 4]\n[6, -12, 2, 8, -4, -10] [-6, 12, -2, -8, 4, 10]\n[6, -12, 2, 8, -4, -10] [4, -6, 10, -2, -8, 12]\n[4, 6, 2] [6, -8, 2, -4]\n[] [2]\n[2] []\n[2] [2]\n[] [6, 4, 2]\n[] [6, -4, 2]\n[] []\n\n# not isotopic\n[] [4, 6, 2]\n[] [6, -8, 2, -4]\n[2] [6, -8, 2, -4]\n[6, -4, 2] [6, -8, 2, -4]";(input.onchange=input.oninput=()=>{input.style.minHeight="1px";input.style.minHeight = (input.scrollHeight)+"px";output.textContent=input.value.split("\n").map(x=>{if(!x.trim()||x.startsWith("#"))return"";let M=/^(\[\s*((?:-?\d+\s*,\s*)*-?\d+\s*)?\]) (?:\[\s*((?:-?\d+\s*,\s*)*-?\d+\s*)?\])$/.exec(x);if(M){let[A,B]=[M[2],M[3]].map(x=>x?x.trim().split(/\s*,\s*/).map(x=>+x):[]);try{f=(
(t,e,l="slice",n=0)=>(f=(t,e=Object.assign([],Object.fromEntries(t[a="map"](((a,f)=>[2*f,a<0?-a:a,a>0])).flatMap((([a,f,p])=>[[a,[--f,p]],[f,[a,!p]]])))))=>n=(f[(e.map((a=>e.find((([a,f],p)=>a==p+1?e=[...e[l](0,p),...e[l](p+2)].map((([a,f])=>[a-2*(a>p),f])):p[f-2*(f>p)-2*(f>a),m])):0)))),e)]||(f[e]=t,f(t,[...e[l](1),...e[l](0,1)].map((([a,f])=>[(a||e.length)-1,f]))),f(t,e.map((([a,f])=>[a,!f]))),(m=[1,-1]).map((a=>m.map((m=>e.map((([e,l],n,[...o],s=o[n+1]?.[0])=>s+1&&l==o[n+1][1]&s+m===o[e+a]?.[0]&&f(t,o,[o[n],o[n+1],o[e],o[e+a],o[s],o[s+m]]=[[s+m,l],[e+a,l],[s,p=o[e+a][1]],[n+1,!l],[e,!p],[n,!l]]))))))),t))!=t|n)(t)|f(e)
);let r=f(A,B);return" ".repeat(M[1].length)+(r?"=":"≠")}catch(e){}}return" ".repeat(x.length+1)+"?"}).join("\n")})()
#input{width:100%;height:100%;border:0;outline:0;padding:5px}body,html{width:100%;height:100%;margin:0}body{overflow:auto}*{box-sizing:border-box}#output{position:absolute;pointer-events:none;top:0;left:0;padding:5px;white-space:pre;opacity:.5}#input,#output{font-family:monospace;font-size:15px;overflow:hidden}

<textarea id="input"></textarea>

Ungolfed / Explanation
// First, we'll convert the dowker notation to a more convenient format:
//   Original:
//     6, -12, 2, 8, -4, -10
//   Reconstruct the pairs:
//     (1,6), (3,-12), (5,2), (7,8), (9,-4), (11,-10)
//   Add in the even pairs:
//     (1,6), (2,-5), (3,-12), (4,-9), (5,2), (6,-1), (7,8), (8,-7), (9,-4), (10,11), (11,-10), (12,3)
//   Extract the sign:
//     (1,6,+), (2,5,-), (3,12,-), (4,9,-), (5,2,+), (6,1,-), (7,8,+), (9,4,-), (10,11,+), (11,10,-), (12,3,+)
//   Zero-index:
//     (0,5,+), (1,4,-), (2,11,-), (3,8,-), (4,1,+), (5,0,-), (6,7,+), (8,3,-), (9,10,+), (10,9,-), (11,2,+)
//   Convert to array: [
//     [5, true],
//     [4, false],
//     [11, false],
//     [8, false],
//     [1, true],
//     [0, false],
//     [7, true],
//     [6, false],
//     [3, false],
//     [10, true],
//     [9, false],
//     [2, true],
//   ]
function dowkerToKnot(dowker) {
  return Array.from({
    // Twice as many pairs
    length: dowker.length * 2,
    ...Object.fromEntries(
      dowker
        .map((x, i) => [i * 2, Math.abs(x) - 1, Math.sign(x) == 1])
        .flatMap(([a, b, x]) => [
          [a, [b, x]],
          [b, [a, !x]],
        ])
    ),
  });
}

// Continually simplify using Reidemeister moves I and II
function simplify(knot) {
  let prev;
  while (prev !== (prev = knot)) {
    for (let a of knot.keys()) {
      /* Reidemeister move I */ {
        // Reidemeister move I resolves simple twists like
        //    \    _
        //   a↘\ /   \
        //      /↙a+1 |
        //     / \ _ /
        //    /
        // This can be expressed by the condition:
        //    a' = a + 1  [where x' is the other number associated with the crossing at x]
        if (knot[a][0] /* a' */ === a + 1) {
          knot = [
            ...knot.slice(0, a), // the crossings before the loop
            ...knot.slice(a + 2), // the crossing after the loop
          ].map(([x, o]) => [x > a ? x - 2 : x /* adjust the indices */, o]);
          break;
        }
      }
      /* Reidemeister move II */ {
        // Reidemeister move II resolves strings tucked under/over another like
        //    \ |
        //   a↘\|
        //      |↑b+1
        //      |\
        //      | |
        //      | |
        //      |/↙a+1
        //    b↑|
        //     /|
        //    / |
        // This can be expressed as the conditions:
        //   b' = a+1
        //   a' = b+1
        //   #a = #(a+1)  [where #x is the over/under sign associated with x]
        let b = knot[a + 1]?.[0]; // (b = (a+1)' is the same as b' = a+1)
        if (
          a < b /* a and b are symmetric, so this dedupes */ &&
          knot[a][1] /* #a */ === knot[a + 1][1] /* #(a+1) */ &&
          knot[a][0] /* a' */ === b + 1
        ) {
          knot = [
            ...knot.slice(0, a) /* before a */,
            ...knot.slice(a + 2, b) /* between a and b (we know that a  [
            x - (x > a) * 2 - (x > b) * 2 /* adjust indices */,
            a,
          ]);
          break;
        }
      }
    }
  }
  return prev;
}

// Applies manipulations to a given knot, calling processKnot for every resulting knot.
// Manipulations include:
//  - Shifting the starting location
//  - Mirroring
//  - Reidemeister move III
function manipulate(knot, processKnot) {
  // Shift the starting location by 1
  if (knot.length)
    processKnot(
      knot
        .slice(1)
        .concat([knot[0]]) /* cycle the crossings */
        .map(([a, b]) => [(a || knot.length) - 1 /* adjust indices */, b])
    );

  // Mirror
  processKnot(knot.map(([a, b]) => [a, !b /* reverse over/under */]));

  /* Reidemeister move III */ {
    // Reidemeister move III moves a string across a crossing:
    //    \     /             \     /
    //     \   /           -------------
    //      \ /                 \ /
    //       /         →         /
    //      / \                 / \
    // -------------           /   \
    //    /     \             /     \
    // Let's label the crossings in the original:
    //      \     /
    //       \   /
    //        \ /
    //     b+1↗/↖c+1
    //        / \
    //    a→ /   \ →a+1
    //  ---------------
    //   b↗/       \↖c
    //    /         \
    // This can be expressed with the following conditions:
    //   b = a'
    //   c = (a+1)'
    //   (b+1)' = c+1
    //   #a = #(a+1)
    // Now, the b and c strings could go in either direction.
    // Here's another example, with b reversed (note b-1):
    //      \     /
    //       \   /
    //        \ /
    //     b-1↙/↖c+1
    //        / \
    //    a→ /   \ →a+1
    //  ---------------
    //   b↙/       \↖c
    //    /         \
    // Thus, we'll generalize the conditions as follows, where B and C are each either 1 or -1:
    //   b = a'
    //   c = (a+1)'
    //   (b+B)' = c+C
    //   #a = #(a+1)

    for (let B of [1, -1])
      for (let C of [1, -1])
        for (let a of knot.keys()) {
          let b = knot[a][0];
          // Edge case: a + 1 might not exist, as it could wrap around the array.
          // This is fine, as it will be handled by one of the knots with a shifted starting location.
          let c = knot[a + 1]?.[0];
          if (
            c != null &&
            knot[a][1] /* #a */ === knot[a + 1][1] /* #(a+1) */ &&
            knot[b + B]?.[0] /* (b+B)' */ === c + C
          ) {
            // Now that we've identified a location to apply this manipulation,
            // we need to determine how to modify the knot.
            // Here's the original knot, again (this uses B=1 and C=1, but is generally applicable):
            //      \     /
            //       \   /
            //        \ /
            //     b+1↗/↖c+1
            //        / \
            //    a→ /   \ →a+1
            //  ---------------
            //   b↗/       \↖c
            //    /         \
            // This needs to be transformed to:
            //    \         /
            //     \       /
            //  ---------------
            //       \   /
            //        \ /
            //         /
            //        / \
            //       /   \
            //      /     \
            // Labeling the crossings, we get:
            //    \         /
            //  a→ \       / →a+1
            //  ---------------
            //   c+1↖\   /↗b+1
            //        \ /
            //       b↗/↖c
            //        / \
            //       /   \
            //      /     \
            let newKnot = [...knot];
            newKnot[a] = [c + C, knot[a][1]];
            newKnot[a + 1] = [b + B, knot[a][1]];
            newKnot[b] = [c, knot[b + B][1]];
            newKnot[b + B] = [a + 1, !knot[a][1]];
            newKnot[c] = [b, !knot[b + B][1]];
            newKnot[c + C] = [a, !knot[a][1]];
            processKnot(newKnot);
          }
        }
  }
}

// We can finally checks if two knots are equal!
function isotopic(a, b) {
  // We'll track the result in this variable.
  let result = false;

  // First, we're going to create an empty object to serve as a record.
  // This will serve as a dictionary from knots to either "a" or "b".
  let record = {};

  function processKnot(knot, marker /* either "a" or "b" */) {
    // Before doing anything with the knot, we'll first simplify it.
    knot = simplify(knot);

    // Then, we'll check to see if the record already has an entry for this knot.
    let value = record[knot];
    if (value === undefined) {
      // We've never seen this knot; add it to the record.
      record[knot] = marker;
      // Process all of the knots we can make by manipulating this one.
      manipulate(knot, newKnot => processKnot(newKnot, marker));
    } else if (value === marker) {
      // We've already seen this knot for this marker, so we don't need to do anything.
    } else {
      // We've already seen this knot, but for a different marker.
      // This means that both original knots (a and b) are isotopic to this knot.
      // By transitivity, they're isotopic to each other as well.
      result = true;
    }
  }

  processKnot(a, "a");
  processKnot(b, "b");

  return result;
}

function main(a, b) {
  // Convert each of a and b to our format and then check if they're isotopic.
  return isotopic(dowkerToKnot(a), dowkerToKnot(b));
}

Nice answer! Although it doesn't save much (I'm sure someone who's skilled in codegolfing in JavaScript could help a lot more), Math.abs(a) can be a<0?-a:a to save 3 bytes. :)

@KevinCruijssen Thanks! Turns out that that whole portion was dumb; I was saving the abs in y to then compare with a to check if it was positive, when a>0 would work just as well. Not sure how I missed that.

I think you can Object.assign([],Object.fromEntries()) instead of Array.from.

@Neil Oh, yep, thanks. I always forget that [].length is a magic auto-updating property.