[Question]
  [
A little while back, data was frequently stored on punched card. A typical card would have 80 columns of 12 possible 'punch' positions. A plethora of encodings was used, each for their own specific purpose.
These days we like to think of a byte as 8 bits. In this challenge, you're tasked to convert an 8-bit byte to a 12-bit word so that it may be stored on a punched card. Since too many holes per column would weaken a punched card considerably, your encoding must adhere to the following rules,
* No more than 3 holes punched per column (i.e., no more than 3 bits set per 12-bit word)
* No more than 2 holes punched consecutively in the same column (i.e., no more than 2 consecutive bits set in each word)
There are [289 valid 12-bit punch cards](https://tio.run/##bdYxTgQxEAXRnLsg9bsTiGwTxPmBzNW73gjLdtvMTNev78@P96/Hz@//b/J7y98ykBlZJnukgFST0nKOHCo3kOu4341epwPrbjLojH1rGchMl3n652QgM7Ksezw/EBnIjCyTPS3g5YnKQGZkmeyRAq3m9f3IQGZkmeyRAlKtpV1evQxkRpbJHikg1aR0z3H7xGQgM7JM9kgBqSal5Zwe6vpdy0BmZJnskQJSTUrLOXJob@DSWdpZ7MbQxujA6hIZtDFOAW1abVptWrMKpGm1abVpWf1zDlVSKCmUFEoKJYWSQklh1qEhhZJCSaGkYLXmubXyTfmmfFO@Kd@Ub8o35ZvyTfmmfFO@mXXr8E35pnxTvinfWNg4z0AZr4xXxivjlfHKeGW8Ml4Zr4xXxivjlfHKeGW8Ml4Zr4w36xmE8cp4ZbwyXhmvjGcB8jxejVCNUI1QjVCNUI1QjVCNUI1QjVCNUI1QjVCNUI1QjVCNUI1QjVCNUI1QjVCNUI1QjVCzHm8iVCNUI1QjVCNUI1QjlJUy5zWqCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmpCakJqQmZ9RpjQmpCakJqQmpCakJqQmpCLCU4345atFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRatFq0WrRZ304sWi1aLVotWi1aLVotWi1aLTof7/wB), of which 1 has 0 holes, 12 have 1 hole, 66 have 2 holes, and 210 have 3 holes. Compare this to 256 8-bit sequences.
You must create two programs/functions: one of which takes a 8-bit byte and converts it to a 12-bit word according to an encoding of your choice that follows the above rules; and one of which takes a 12-bit word according to your encoding and converts it back to an 8-bit byte. The latter program may exhibit undefined behaviour for any input which is not contained in your encoding.
Input and output is flexible according to the defaults for I/O, and may even differ between your programs. Your score is the sum of the bytecount of both programs. The programs must work independently, so they may not 'share' code between them.
  
[Answer]
# JavaScript (ES6), ~~103~~ 100 bytes
*See also [my other answer](https://codegolf.stackexchange.com/a/211106/58563) for less bitwise operations, more maths, and a closed-form expression*
Both functions expect and return an integer.
## 8-bit → 12-bit (~~56~~ 53 bytes)
```
f=n=>n&&(w=f(n-1),w+=(x=w&w-1)&x-1?w-x:1)+(w&w/2&w/4)
```
[Try it online!](https://tio.run/##bY5NbsJADIX3nMKrxNYkU5LSLpoOHIJly2IECQQhTzSZdiJVcPXgwAIWXfjnWZ@f3tH@2n7r2y7k7Hb1ODaGzZKTBKNpkPOCsqgMDiYmUUQy5MUq5sNHQQrl9FJKLWhsnEcLBr42GbDMeSXjE8q3d1mUIvibAWwd9@5U65PbI@vg1sG3vEfSnd2tg/UBXwkUpHBZSlOAvu7FS2IQ/Ysv7jhOsLAPpnyCijKD@Q2klCqJYXX30x8mc5Hn2XOq9JsnL6uPrmVMM/mharwC "JavaScript (Node.js) – Try It Online")
### How?
This is a recursive function that computes the next valid 12-bit word \$w\_{n+1}\$ directly from the previous valid word \$w\_n\$.
**Step 1: Update `w` according to the total number of bits set**
Given `w` from the previous iteration we first compute:
```
x = w & (w - 1)
```
which is `w` without its least significant bit.
Likewise, we remove the least significant bit from `x` by doing:
```
x & (x - 1)
```
If the result is zero, we just increment `w`.
If the result is non-zero, it means that there are already 3 bits set in `w` and we are not allowed to insert a 4th one. In that case, we add the least significant bit of `w` (which is `w - x`) to `w`. This results in at most as many bits set in the updated value than in the original one, or less in case of carry propagation.
Examples:
```
110010 +  10 -> 110100
101100 + 100 -> 110000
```
**Step 2: Update `w` according to the number of consecutive bits set**
We now have to make sure that there are not 3 consecutive bits set in `w`. We do that by adding the result of a bitwise AND between `w`, `w / 2` and `w / 4`.
Example:
```
01110 + (01110 & 00111 & 00011) -> 01110 + 10 -> 10000
```
## 12-bit → 8-bit (47 bytes)
```
f=w=>w&&!(w&w/2&w/4|(y=(x=w&--w)&x-1)&y-1)+f(w)
```
[Try it online!](https://tio.run/##bVTLbhsxDLz3K9SLuwuvXZESJS0C59YvyLHowUjiNEVgB47RTYCiv@5yRtcedgANKYqP4f7a/96/3Z@fXy@b4@nh8Xo97Jbd7bJafR6W1fJV/ct/ho/d8L5bVpvNMq7eNzKuPhzWh2EZrzff4xRkCjqFNIU8BZtCmUKbwuw8bG4Ut4qbxS1S/XOzuF3drrjsdvW76pfVfRKiuX9yLjmXwPm95Pey38l@J7tPdnsG57HMeXOuOFeQhN8pfqe4vbi9ur26vXrc6j4VWTrXPFZzvjnXkLbzs9@bkWNEATGjAhwVNSVwCVUlloW6kKggS0GagjwFiQoylGwAWJGsIEspsBZEKTzCD6kK8pIZ3IzIMwzISZGLxoYu4WgVwCM6iXiKeIp4inha2E44I7KiekX5ivoVDyk6oChbG6LMOOJxxZMJlScMKWFKCWNKSq5hQHBBMQm9Sw2DauAQLyFKjhyWcwYJGDRgApGIAWiogIbhcYICgDMeMqUB40XbDR03vGtorKGxlnmEH3IxNMJQm1VErjCgSkOShqwKmlgwngIhlUzVQDaIUozCgVIiZdIAEBAVRAPCVwoGBTbqICJfRyMWYiVSSJROpHZi96dwIpUTKZ2Yu9qESB/KJ1I10ehjjGlkqKFI6cRK/0a@8a2ZViopYprShSzKpaQIxRKRPON3XXYlSqPwubQUiiOXGevi2Nei7wWl39e8Sz5S6azLuBPGpTDWyOkJ5@XPNq4D94GyZb2O/EEYVW9CVGIi0pObwJ4oe6LsicYeh/qPXIDIDWCXfIf6JjFm6wz9uQfslQpqcUzETCTff1mYqXK5lQvrWInkW/zxaXs4nb/t738Ox7C7Dfen49vp5XH7cnoajtvL6e5yfj4@DeP2df9wd9mfL0Mewzp8CX9vHdbhMBzH/7qlcRxvrv8A "JavaScript (Node.js) – Try It Online")
### How?
In this version, we decrement `w` at each iteration and increment the final result whenever `w` is a valid 12-bit word. We stop when `w = 0`.
We use the following methods to test the validity of `w`. They are similar to the ones used above.
* we compute `w & w / 2 & w / 4` to make sure that there are not 3 consecutive bits set
* we compute a chain of three `w & (w - 1)` to make sure that there are no more than 3 bits set
[Answer]
# [APL (Dyalog Unicode)](https://www.dyalog.com/), 21 + 22 = ~~62~~ 43 bytes
```
⎕⌷⍋(+/+3∊3+/⊢)¨,⍳12/2
```
and
```
⎕⍳⍨⍋(+/+3∊3+/⊢)¨,⍳12/2
```
[Try it online!](https://tio.run/##SyzI0U2pTMzJT///qG@qp/@jtgkG/x91tKdxAbmPerY/6u3W0NbXNn7U0WWsrf@oa5HmoRU6j3o3GxrpG3EB1YFwOlht7@ZHvSsIKf//H2hwVxNQxMjUDAA "APL (Dyalog Unicode) – Try It Online")
Pretty much the same programs, with indexing into an array (`⌷`) of valid punch cards for the encoder and finding the index (`⍳⍨`) for the decoder. Both the encoder and the decoder take a number and output a number.
### Explanation
```
                ,⍳12/2     ⍝ Create every combination of 12 bits
    (         )¨           ⍝ Map each to
          3+/⊢             ⍝ Does the sum of each consecutive group of three bits
        3∊                 ⍝ Contain a 3 (1 if so, else 0)
     +/+                   ⍝ Add that to the total digit sum
   ⍋                       ⍝ And grade this list ascending
                           ⍝ This outputs the indexes (which are the same as parsing the original bits in base 2) in order of the smallest value to the largest
⎕⌷                         ⍝ Finally we either index the input into the list
⎕⍳⍨                        ⍝ Or find the index of the input in the list
```
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), 13 + 12 = 25 [bytes](https://github.com/DennisMitchell/jelly/wiki/Code-page)
### 8-bit byte (integer) -> 12-bit word (list of 1-indexed set bits):
```
3Ḋ12œcẎIÞṚị@‘
```
**[Try it online!](https://tio.run/##ASUA2v9qZWxsef//M@G4ijEyxZNj4bqOScOe4bma4buLQOKAmP///zE0 "Jelly – Try It Online")**
### 12-bit word (list of 1-indexed set bits) -> 8-bit byte (integer):
```
3Ḋ12œcẎIÞṚḊi
```
**[Try it online!](https://tio.run/##y0rNyan8/9/44Y4uQ6Ojk5Mf7urzPDzv4c5ZQIHM////RxvpKBgaALFhLAA "Jelly – Try It Online")**
[Here](https://tio.run/##y0rNyan8/9/o4Y5tD3dONzQKtjm09Fi7SYinyuF5D3fOeri72@FRw4z/RqZmQBVH9xxuf9S0RuXopIc7ZwAZQOT@HwA) is a map (`byte-integer word-as-12-bits word-as-1-indexed-set-bits`) using the first Link.
### How?
Both Links create a list of all (\$286\$) ways to punch either two or three holes as lists of 1-indexed holes (e.g. `[3,7]` represents `001000100000`), sorts these by the incremental differences between those indices (i.e. `[3,7]` (`001000100000`) maps to `[4]` while `[2,3,4]` (`011100000000`) maps to `[1,1]`), and reverses that result (placing the \$11\$ of the form `0*110*` at the very end, preceded by the \$10\$ (invalid ones) of the form `0*1110*`, with \$265\$ usable patterns at the start).
The first Link finds the 0-indexed entry, while the second finds the 1-indexed index of the representation of a word in a dequeued version of the list (this then gives us a `0` when the input is not found).
```
3Ḋ12œcẎIÞṚ - niladic chain
3          - three
 Ḋ         - dequeued -> [2,3]
  12       - twelve
    œc     - combinations (vectorises)
             -> [[[1,2]…[1,12],[2,3]…[11,12]],[[1,2,3],[1,2,4]…[10,11,12]]]
      Ẏ    - tighten
             -> [[1,2]…[1,12],[2,3]…[11,12],[1,2,3],[1,2,4]…[10,11,12]]
        Þ  - sort by:
       I   -   incremental differences
               -> [[1,2]…[11,12],[1,2,3]…[10,11,12],[1,2,4]…[1,12]]
         Ṛ - reverse
               -> [[1,12]…[1,2,4],[10,11,12]…[1,2,3],[11,12]…[1,2]]
                  <- 265 valid -> <-   10 invalid  -> <- 11 valid->
ị@‘ - rest of Link 1: byte (integer), b
  ‘ - increment -> b+1
 @  - with swapped arguments:
ị   -   (b+1) 1-indexed index into (the list above)
Ḋi  - rest of Link 2: word (list of hole indexes), w
Ḋ   - dequeue (the list above) - remove the first word (representing the zero byte)
 i  - index of (w) (in that) - yields zero if not found
```
[Answer]
# [Python 3](https://docs.python.org/3/), 160 bytes
---
# [Python 3](https://docs.python.org/3/), 82 bytes
```
f=lambda b,n=0:0**b*f'{n:012b}'or f(b-(sum(map(bin(~n).count,('111','1')))<4),n+1)
```
[Try it online!](https://tio.run/##HdPLbhpREIThfT3F7JhxHOtUn7sVL51tNnkB44CMYgY0DEqsKHl1p9pIXHTUwPD9zfltfTnN8f1wPJ@Wdbi8XaD73WW3Lrvn63I5nObXw/Gwjgx@m973D69Px@2Pp2F7Oz@E@3Bzs73Zb/7M94G2/bs5LcN@3H4eL9fjeHw6j9vDPP6bp7vn03Veb8cNyc3thptpmr6k6Xb@xOn918vhdTd8X667ewzDurz50zAc5vPwoMd11KvrOk7Tx/F58aO9H36c7H4/787r8Pjt6@OynJb7Ybvsnn6@BxCGiISMgoqGDuqQoIERTGAGC1jBBnZYgOk9BouwBMuwAquwBuuIAZGI@siImBAzYkGsiA2xIwUkIhmSvjEhZaSCVJEaUkcOyEQ25IisC8rIBbkiN@SOElCIYigRJaHoegtKRWkoHTWgEtVQI2pCzaj6ORW1oXa0gEY0Q4toCS2jFTT92obW0QM60Q09oif0jF7QK7owXEMcQR5BIEEiQSRBJkEoQSpBLEFzH2yacziXczq3czzXcz75UYA099WcDClESpFipBwpSEqSoqQsGT2E5sRJeVKglChFSplSqJQqxcrkxTQnWYqWsqVwKV2Kl/KlgClhZk@rOSFTyhQz5UxBU9IUNWVNYbP4DmhO3hQ4JU6RU@YUOqVOsVPurL4smhM9ZU/hU/oUP@VPBaAKUAnYfKs0pwpUBqoDFYIqQaWgWlAxqBrsvn6@f1pA9TD1MPUw9TD1MPUw9TD1MPUw@qJqTj1MPUw9TD1MPUw9TD3M99kX@mOjNec77UvtW@1r7Xvti60eph6mHhZ99TWnHqYeph6mHqYeph6mHqYeph6W/D@iOfUw9TD1MPUw9TD1MPUw9TD1sOx/pvwf "Python 3 – Try It Online")
# [Python 3](https://docs.python.org/3/), 78 bytes
```
[f'{i:012b}'for i in range(2181)if sum(map(bin(i).count,('111','1')))<4].index
```
[Try it online!](https://tio.run/##VdZNTxNhGEbhPb9idm0TYuZCF4bIErdu3KkLwAITpW3GNkKMvx3B1SkLPgqdvLzP/Zz77J7299vN2@e7i6/PX24Xf6bz0dn138Xtdh6mYdoM89Xmbr08895quh1@HR6WD1e75fW0WU6rNzfbw2Z/ulxgcbqwWK1WH959ezNtvq8fn3/fTz/Xw@f5sD4/GYb9/PT6ZXh55m64eP182C9X/1/ZzdNmv7xbvry2en1l/Xiz3u2Hy08fL@d5O58P1/P66sfzmI@TfC8/6G/kN/oefY@8Rx@tjzYe/VkerY@WR@tB9aB6UD2oHtR49LQcVA@qB5WD6lXpVelV6VXpVelV6VXpVelVGY9OkNvRC9EL0QuRO9Bp67R12jptnbZOW6et09Zp67R1wDpTnanO1Hj0L2RYOhIdiY5EpyB3rfHX@Gv8Nf4af42/xl/jr/HX@GviNeQacg255lrTqxnVjGpGNZbGowtJqjQuGheNiyZEcyADVtQoahQ1ihpFjaJGUaOoUdQoapQuChQFigJFGaKkUB4oD5QHigBddN1gXU1dTV1N3UbdOePRXWcxNOQacg255lrTq7GUiGl9aH1ofWh9aH1ofWh9aH1ofWh9aGNoSWhJaEloLyj9lfHKeGW8Yl3hrVRW3CpuFbdKWOWoAlJhp@BScCm4lFVKJEWN8Wim2XpdWl1aXVrdU91GXTNdGUm8eoh6iHqIeoh6iHqIeoh6iHqIeoiqh9qG2obahgqGaoTKgsqCyoL6gVqA1rv2tva29rZWtRayNq22pjagNqA2oJaeVpt2lvaP1ofSX@mv9FfgK9aV18pe41GQAjvFk@JJ8aREUu4oUBQOutuyjarUqtSq1KrUqtSq1KrUqtSq1KrUatEqzirOKs7qymrE6r3qveq9qroqtGqqqqCqoKqgap3qliqNKoAqcypzKnPZmPEf "Python 3 – Try It Online")
The first function (8-to-12-bit) increments numbers resursively until the binary representation satisfies all criteria. The second function (12-to-8-bit) uses the `index` method of a filtered list.
The list in the second function has a `__getitem__` method which could replace the first function, but this is 2 bytes longer.
Thanks to ovs and xnor for saving 6 bytes in total.
[Answer]
# JavaScript (ES7), ~~167 144~~ 143 bytes
### 8-bit → 12-bit (~~120 97~~ 96 bytes)
This answer is definitely not very competitive, but I was curious to see how the conversion could be done without any recursion nor trial-and-error.
So here is a closed-form expression!
Expects and returns an integer.
```
n=>n<5?n:1<<(k=(54*n-166)**.333/2.1+.17)|2**(q=((n+=~~k*~k*~-k/6-3)*8)**.5-1>>1)|2**(n+q*~q/2-2)
```
[Try it online!](https://tio.run/##bc7NTsMwDAfw@57CtzpJky7pWhD94CF2BA7R1m5dJ6dfcAH66iVlB3ZAsmJH@vkvX@yHHQ9D002S3LFa6mKhoqQ8eaYnnefYFpjsOEmdpoxzFcdxZJQWSj@wL8M59gUiiWKeW76WbKNUxow/rjiRuiz1zZHo@dxHRhq21G5ACwW8vIVAvm8z33IwSeoHIRh8bgAOjkZ3rdTVnZDU5PbT0NAJmerscT/ZYcKYgYAA5tI/AnCoRp9VIzH2L9/dOK7Y2z9j7pA2IWx/IQtY5s@wqnsfz2u4/35v7q8KXmnNsuriGsIg9DssW34A "JavaScript (Node.js) – Try It Online")
### How?
This is based on the observation that the positions of the bits in a valid 12-bit word are correlated to tetrahedral and triangular numbers.
If \$n\$ is less than \$5\$, we just return \$n\$. Otherwise, we go through the following steps:
* We first figure out what is the lowest \$k\$ such that \$T(k-1)\ge n-3\$, where \$T(k)\$ is the \$k\$-th [tetrahedral number](https://oeis.org/A000292), defined as:
$$T(k)=\frac{k(k+1)(k+2)}{6}$$
\$k\$ is the real root of the following cubic equation, rounded towards \$0\$:
$$x^3 + 3x^2 + 2x - 6(n-3) = 0$$
Once solved with WolframAlpha's [Cubic Equation Solver](https://www.wolframalpha.com/widgets/view.jsp?id=578d50248844454e46e24e9ed230843d) and after a few approximations (safe for our operating range), we end up with:
$$\cases{
t=\left(\sqrt{\strut729(n-3)^2-3}+27(n-3)\right)^{1/3}\\
k = \left\lfloor\dfrac{t}{2.08} + \dfrac{0.694}{t}\right\rfloor
}$$
Fortunately, we don't need so much precision and it can be simplified to the following JS expression, which gives [the correct result](https://tio.run/##LU/RUoMwEHznK7YzDk3AYiEFZ0T02Qef/IFiAItlEgewMHbor@Nd6UOS27273c13fso73dY//cbYopznChkMshe6XBdiIFgJgw1CeY8BfgYxEjfApcMsFSMXeL0SI56Y9HmVRx4Q3d6ddJzKtiSWIU5J/xlRnFDh@xJnBxhsWyx2MiX4WfeEVEii73l/CHTzpyLBQ9Sm/pG6l4sQ8Q4ergmTRMLzsA2UUmwchJRjG4SPy0JdCdZcZTguhkB/aO2A/Zs55U1dgOLx53F3NtOeM0zO5GhrOtuUQWO/xPrjV@uy61Zrmc7zPw) for \$4<n<256\$:
```
k = ~~((54 * n - 166) ** 0.333 / 2.1 + 0.17)
```
* \$k\$ is the 0-indexed position of the leading bit in the punch card word.
* We define:
$$p=n-3-T(k-1)$$
* If \$p>0\$, the position of the 2nd bit in the punch card word is given by:
$$q=\left\lfloor\dfrac{\sqrt{8p}-1}{2}\right\rfloor$$
which is based on the reverse formula for triangular numbers.
The JS implementation uses a right-shift. It means that we actually get \$-1\$ if \$p=0\$, in which case there's no 2nd bit.
* The position of the 3rd bit in the punch card word is:
$$r=p-\frac{q(q+1)}{2}-2$$
There's no 3rd bit if \$r<0\$.
We use `… | 2 ** expr` rather than `… | 1 << expr` so that negative bit positions are ignored as expected.
## Hybrid version (90 bytes)
For the record, below is my first attempt which was still using a limited number of recursive calls (up to 12) to compute \$k\$.
```
n=>n<4?n:(k=0,g=n=>n<0?2**~-k|2**(q=(p*8)**.5-1>>1)|2**(p+q*~q/2-2):g(n-k*++k/2,p=n))(n-3)
```
[Try it online!](https://tio.run/##bY5NcoMwDIX3OYV2WLZxwEk6nRCTQ2TZduFJgBAytvlpN225OhV00Sy60dPTfHrSzX7Y/tzVYYidvxRTaSZncnfYHt2eNSaRlVl8ctScj3HzRcJawwJ/Rs7VLk7zPMVlGkTLx3atY437irm44UI0ay2DcYjkNziVvmMWDLy8SXCkSUZyAL17okYIhM8VwNm73t8LdfeUogZ/GrraVQxVsJfTYLuBbRAERDDmVASwrugpq2R05l98@4uzGSb2j9EPUKolJAuIEWb0hlXhvb/O4WS/V49fRa9uzrLq5mvHIkk7mE0/ "JavaScript (Node.js) – Try It Online")
---
## 12-bit → 8-bit (47 bytes)
I didn't write a specific 12-bit to 8-bit converter for this answer. But because the sequence is exactly the same, we can just use [the one we already have](https://codegolf.stackexchange.com/a/211082/58563).
```
f=w=>w&&!(w&w/2&w/4|(y=(x=w&--w)&x-1)&y-1)+f(w)
```
[Try it online!](https://tio.run/##bVTLbhsxDLz3K9SLuwuvXZESJS0C59YvyLHowUjiNEVgB47RTYCiv@5yRtcedgANKYqP4f7a/96/3Z@fXy@b4@nh8Xo97Jbd7bJafR6W1fJV/ct/ho/d8L5bVpvNMq7eNzKuPhzWh2EZrzff4xRkCjqFNIU8BZtCmUKbwuw8bG4Ut4qbxS1S/XOzuF3drrjsdvW76pfVfRKiuX9yLjmXwPm95Pey38l@J7tPdnsG57HMeXOuOFeQhN8pfqe4vbi9ur26vXrc6j4VWTrXPFZzvjnXkLbzs9@bkWNEATGjAhwVNSVwCVUlloW6kKggS0GagjwFiQoylGwAWJGsIEspsBZEKTzCD6kK8pIZ3IzIMwzISZGLxoYu4WgVwCM6iXiKeIp4inha2E44I7KiekX5ivoVDyk6oChbG6LMOOJxxZMJlScMKWFKCWNKSq5hQHBBMQm9Sw2DauAQLyFKjhyWcwYJGDRgApGIAWiogIbhcYICgDMeMqUB40XbDR03vGtorKGxlnmEH3IxNMJQm1VErjCgSkOShqwKmlgwngIhlUzVQDaIUozCgVIiZdIAEBAVRAPCVwoGBTbqICJfRyMWYiVSSJROpHZi96dwIpUTKZ2Yu9qESB/KJ1I10ehjjGlkqKFI6cRK/0a@8a2ZViopYprShSzKpaQIxRKRPON3XXYlSqPwubQUiiOXGevi2Nei7wWl39e8Sz5S6azLuBPGpTDWyOkJ5@XPNq4D94GyZb2O/EEYVW9CVGIi0pObwJ4oe6LsicYeh/qPXIDIDWCXfIf6JjFm6wz9uQfslQpqcUzETCTff1mYqXK5lQvrWInkW/zxaXs4nb/t738Ox7C7Dfen49vp5XH7cnoajtvL6e5yfj4@DeP2df9wd9mfL0Mewzp8CX9vHdbhMBzH/7qlcRxvrv8A "JavaScript (Node.js) – Try It Online")
[Answer]
# [Brachylog](https://github.com/JCumin/Brachylog), 27 + 27 = 54 bytes
```
∧{ḃs₃ᵇ+ᵐ≤ᵛ2&ḃ+≤3&≜}ᶠ²⁵⁶;?∋₎
```
[Try it online!](https://tio.run/##SypKTM6ozMlPN/r//1HH8uqHO5qLHzU1P9zarv1w64RHnUsebp1tpAYU1QayjdUedc6pfbhtwaFNjxq3PmrcZm3/qKP7UVPf//9Gpqb/owA "Brachylog – Try It Online")
It's the same program! To decode `2240`, give it as an argument and set the input to `Z` like [here](https://tio.run/##SypKTM6ozMlPN/r//1HH8uqHO5qLHzU1P9zarv1w64RHnUsebp1tpAYU1QayjdUedc6pfbhtwaFNjxq3PmrcZm3/qKP7UVPf//9R/42MTAwA).
### How it works
```
∧{ḃs₃ᵇ+ᵐ≤ᵛ2&ḃ+≤3&≜}ᶠ²⁵⁶;?∋₎
∧{                }ᶠ²⁵⁶     get the first 256 values of …
  ḃ                          in the base 2 representation …
   s₃ᵇ                        every 3 consecutive elements …
      +ᵐ≤ᵛ2                   has a sum ≤ 2
           &ḃ+≤3             and the base representation has a sum ≤ 3
                &≜           turn the constraints to a number
                       ;?∋₎ the number at index (input) is the output
```
[Answer]
# [Charcoal](https://github.com/somebody1234/Charcoal), 21 + 21 = 42 bytes
### Encoder:
```
Ｉ§Φ×³φ››⁴Σ⍘ι²№⍘ι²111Ｎ
```
[Try it online!](https://tio.run/##S85ILErOT8z5/z@gKDOvRMM5sbhEw7HEMy8ltULDLTOnJLVIIyQzN7VYw1hHwdDAwEBTR8G9KDURJA6jTXQUgktzNZwSi1ODS4CmpGtk6igYaWoClTrnlwINRZfRUVAyNDRUAqvwzCsoLfErzU0CGgQUsP7/38jU9L9uWQ4A "Charcoal – Try It Online") Link is to verbose version of code.
### Decoder:
```
Ｉ⌕Φ×³φ››⁴Σ⍘ι²№⍘ι²111Ｎ
```
[Try it online!](https://tio.run/##S85ILErOT8z5/z@gKDOvRMM5sbhEwy0zLwVI5JSkFmmEZOamFmsY6ygYGhgYaOoouBelJoLEYbSJjkJwaa6GU2JxanAJ0Ih0jUwdBSNNTaBS5/xSoInoMjoKSoaGhkpgFZ55BaUlfqW5SUCDgALW//8bGVoY/NctywEA "Charcoal – Try It Online") Link is to verbose version of code.
The only difference between the two programs is the second byte, which is `§` (AtIndex) for the encoder and `⌕` (Find) for the decoder. Explanation:
```
     φ                  Predefined variable 1000
   ×³                   Multiplied by 3
  Φ                     Filter on implicit range
        ⁴               Literal `4`
       ›                Is greater than
           ι            Current index
          ⍘ ²           Convert to a binary string
         Σ              Digital sum
      ›                 And not
               ι        Current index
              ⍘ ²       Convert to a binary string
             №          Count (contains)
                 111    Literal string `111`
                    Ｎ   Input as a number
 §                      Index into filtered results
 ⌕                      Find index in filtered results
Ｉ                       Cast to string
                        Implicitly print
```
[Answer]
# [C (gcc)](https://gcc.gnu.org/), 64 + 64 = 128 bytes
Saved ~~2~~ 6 bytes thanks to [Dominic van Essen](https://codegolf.stackexchange.com/users/95126/dominic-van-essen)!!!  
Saved 9 bytes thanks to the man himself [Arnauld](https://codegolf.stackexchange.com/users/58563/arnauld)!!!  
Saved 2 bytes thanks to [ceilingcat](https://codegolf.stackexchange.com/users/52904/ceilingcat)!!!
### Encode \$8\$-bit to \$12\$-bit ~~85~~ \$\cdots\$ ~~66~~ 64 bytes
```
i;e(n){for(i=1;n;)n-=__builtin_popcount(++i)<4&i/(i&-i)!=7;n=i;}
```
[Try it online!](https://tio.run/##bZHdboJAEIXveYopibAr3Sj@tGlX6EXTp1BD7LK2S9rBrJiYGJ6dDgUUazchw2TO@c4OKPGhVFUZqRny0za3zEShRMlRREnyfjBfhcFkl@9UfsCCBYHhi5lnRsx4wvC76FFiZGRZZTLtAFk0lkIQIgv@Q2ANwBFDT2ALyGTpOAYL@N4YZNw5OUBHfW7sEOxyDRGc3NXxbbI6Pr3SM3fvod9P3VL@OigdWM0p9L5IkHxj2b0vYDJ/kBAETc@hCanP2aHJoVkrkLfzlOZpM9d/5iq3VquCBJ0y6pI9r6PHMYSTejK@mHeW7FvmDqYpiBgGs6ZQx2jNBtFW3da0l31e2VB0GEowMX1@EPRzehteBSniMna5Eik9COEF/KEPz@CDz3sB5c1NOQz2KySIXbZbr1t96ZTVDw "C (gcc) – Try It Online")
### Decode \$12\$-bit to \$8\$-bit ~~85~~ \$\cdots\$ ~~68~~ 64 bytes
```
j;d(n){for(j=0;--n;)j+=__builtin_popcount(n)<4&n/(n&-n)!=7;n=j;}
```
[Try it online!](https://tio.run/##bVHrToMwFP7PUxxJBu2w2dhFo13xh/EptoXM0mmJFtKxZAnh2fEgsDFnk@b05Lv1tJJ9SFnXKU@IoeU@syQVU86Y4TQNRBy/H/VXoU2cZ7nMjqZA1mrhmQkxHjP0TjxyI1Je1Zqr3kCLkKPcsP/kQaAbAz0h2mO6M9C8chxtCvjeaUOoUzqAS37u7BjsegsCSndzepttTk@vuJfuPQz7uVvxXwWmA2l8CnUoYoO6Ke/PK5gtHzgEQdtTaEOadVYoVCjSEfgtniCetLj6g8vMWiULJPRM0Sd7Xu8eRRDOGmR6EecW5XvijuYJsAhGi7ZgR3DM1qKrqqvJIPs8ssboMOSgI/w/YPi2gwmvgiT6EnK5EjI9COEF/LEPz@CDTwcB1c1NKYwOG4Mmdt1Nve34lVPVPw "C (gcc) – Try It Online")
### Explanation
Sequentially goes through integers starting at \$1\$ only counting ones with valid bit patterns and returns the \$n^{th}\$ valid integer from \$1\$ for encoding, \$e(n)\$, \$8\$-bit to \$12\$-bit. Counts the number of valid integers from \$1\$ up to \$n\$ for decoding, \$d(n)\$, \$12\$-bit to \$8\$-bit.
### GCC builtins used
`__builtin_popcount(n)` returns the number of 1-bits in \$n\$.
[Answer]
# [Japt](https://github.com/ETHproductions/japt), 17 + ~~19~~ 17 = ~~36~~ 34 [bytes](https://en.wikipedia.org/wiki/ISO/IEC_8859-1)
I'm not sure I've fully understood the challenge, but let's give it a go ...
## 8-bit to 12-bit, 17 bytes
Input as an integer, output as a binary string.
```
gI²o¤fÈè1 §3«Xø#o
```
[Try it](https://petershaggynoble.github.io/Japt-Interpreter/?v=1.4.6&code=Z0myb6RmyOgxIKczq1j4I28&input=MjU1) or [run all test cases](https://petershaggynoble.github.io/Japt-Interpreter/?v=1.4.6&code=Z0myb6RmyOgxIKczq1j4I28&footer=VnMg%2bTMgKyIgLT4ge/lDfSAoPSB7zXMg%2bTR9KQ&input=MjU2Ci1tUg)
## 12-bit to 8-bit, ~~19~~ 17 bytes
Input as a binary string, output as an integer.
```
nI²o¤fÈè1 §3«Xø#o
```
[Try it](https://petershaggynoble.github.io/Japt-Interpreter/?v=2.0a0&code=bkmyb6RmyOgxIKczq1j4I28&input=IjEwMDAxMDAwMDEwMCI) or [run all test cases](https://petershaggynoble.github.io/Japt-Interpreter/?v=2.0a0&header=Z0myb6RmyOgxIKczq1j4I28&code=bkmyb6RmyOgxIKczq1j4I28&footer=%2bUMgKyIgKD0ge81zIPk0fSkgLT4gIitWcyD5Mw&input=MjU2Ci1tUg)
## Explanation
```
gI²o¤fÈè1 §3«Xø#o     :Implicit input of integer (first programme)
nI²o¤fÈè1 §3«Xø#o     :Implicit input of binary string (second programme)
g                     :Index into (first programme)
n                     :Convert from base (second programme)
 I                    :64
  ²                   :Squared
   o                  :Range [0,I²)
    ¤                 :To binary strings
     f                :Filter by
      È               :Passing each X through the following function
       è1             :  Count the "1"s
          §3          :  Less than or equal to 3
            «         :  AND NOT
             Xø       :  X contains
               #o     :  111
                 Ã    :End filter (second programme)
                  bU  :First 0-based index of U (second programme)
```
[Answer]
# [05AB1E](https://github.com/Adriandmen/05AB1E/wiki/Commands), 36 (18 + 18) [bytes](https://github.com/Adriandmen/05AB1E/wiki/Codepage)
**8-bit (integer) to 12-bit (string of 0s/1s) (18 [bytes](https://github.com/Adriandmen/05AB1E/wiki/Codepage)):**
```
T12ãʒ1¢4‹}ʒƵAå≠}Iè
```
[Try it online](https://tio.run/##ASkA1v9vc2FiaWX//1QxMsOjypIxwqI04oC5fcqSxrVBw6XiiaB9ScOo//8yMA) or [verify all test cases](https://tio.run/##AToAxf9vc2FiaWX/VDEyw6PKkjHCojTigLl9ypLGtUHDpeKJoH1VCuKChcaSTj8iCeKGkiAiP/9YTsOo/yz/).
**12-bit (string of 0s/1s) to 8-bit (integer) (18 [bytes](https://github.com/Adriandmen/05AB1E/wiki/Codepage)):**
```
T12ãʒ1¢4‹}ʒƵAå≠}Ik
```
[Try it online](https://tio.run/##yy9OTMpM/f8/xNDo8OJTkwwPLTJ51LCz9tSkY1sdDy991Lmg1jP7/39DAwNDIAYDAA) or [verify all test cases](https://tio.run/##ATsAxP9vc2FiaWX/VDEyw6PKkjHCojTigLl9ypLGtUHDpeKJoH1EVeKCgcKjCnZ5PyIg4oaSICI//1h5a/8s/w).
The only difference is the last byte (`è` vs `k`).
**Explanation:**
```
T           # Push 10
 12ã        # Take the cartesian product of "10" and 12:
            #  ["111111111111","111111111110","111111111101",...,"000000000000"]
    ʒ       # Filter it by:
     1¢     #  Count the amount of 1s
       4‹   #  And check that this count is smaller than 4
    }ʒ      # Filter again:
      ƵA    #  Push compressed integer 111
        å≠  #  And check that the string does NOT contain "111" as substring
     }I     # After the second filter: push the input
       è    # (8-bit to 12-bit) Get the string at the 0-based input index
       k    # (12-bit to 8-bit) Get the 0-based index of the input-string
            # (after which it is output implicitly as result)
```
[See this 05AB1E tip of mine (section *How to compress large integers?*)](https://codegolf.stackexchange.com/a/166851/52210) to understand why `ƵA` is `111`.
[Answer]
# [R](https://www.r-project.org/), 42 + 61 = 103 bytes
**Encoder**
```
(a=combn(13,3)-1)[,a[1,]+2<a[3,]][,scan()]
```
[Try it online!](https://tio.run/##K/r/XyPRNjk/NylPw9BYx1hT11AzWicx2lAnVtvIJjHaWCc2NlqnODkxT0Mz9r@hgcF/AA "R – Try It Online")
Input as decimal number; outputs 3 positions to punch holes, with zero indicating no hole to punch.
**Decoder**
```
which(colSums(!((a=combn(13,3)-1)[,a[1,]+2<a[3,]]-scan()))>2)
```
[Try it online!](https://tio.run/##K/r/vzwjMzlDIzk/J7g0t1hDUUMj0TY5PzcpT8PQWMdYU9dQM1onMdpQJ1bbyCYx2lgnNla3ODkxT0NTU9POSPO/oYKZgsV/AA "R – Try It Online")
Input as 3 positions of punched holes, in ascending order, prefixed with zero if only two holes are punched; outputs as decimal number.
**How?**
`a=combn(13,3)` generates all picks of 3 integers from `1..13`, without repetitions, in ascending order. We subtract 1 to get all 2- and 3-hole combinations.  
`a=a[a[1,]+2<a[3,]]` keeps only combinations where the lowest minus the highest position is less than 2, thereby removing any sets of 3 holes in a row.  
To encode `b`, we choose the `b`th combination.
To decode `w`, we subtract `w` from each combination: the correct combination is now all-zero, so its logical inverse is all-TRUE, so we select the combination that sums to 3.
[Answer]
# [Pyth](https://github.com/isaacg1/pyth), 14 + 14 = 28 bytes
Encoding:
```
@sDoS.+N^U2 12
```
Decoding:
```
xsDoS.+N^U2 12
```
[Try encoding online!](https://tio.run/##K6gsyfj/36HYJT9YT9svLtRIwdDo/39DY2MA "Pyth – Try It Online") [Try decoding online!](https://tio.run/##K6gsyfj/v6LYJT9YT9svLtRIwdDo//9oAx0FIDIEk8gMBDcWAA "Pyth – Try It Online")
Both programs rely on the snippet `sDoS.+N^U2 12`, which creates a list of all possible length-12 lists of `0`s and `1`s, whose first 256 entries are all valid punch cards. This can be verified with this program: [Verify it online!](https://tio.run/##K6gsyfj/P63GrjjEuDbXUMFYzyrE2KbYJT9YT9svLtRIwdBIwcjU7P9/AA "Pyth – Try It Online").
How the snippet works:
```
sDoS.+N^U2 12
       ^U2 12    Generate all length-12 lists of 0s and 1s.
  o              Sort by
    .+N            The deltas of the length-12 list
   S               sorted
sD               Sort by the sum of the length-12 list.
```
Sorting on the sorted deltas of the length-12 list moves lists with more alternations between runs of `0`s and `1`s to the front. This moves the lists with 3 `1`s in a row as their only `1`s to the back. Sorting on the sum of the length-12 list then brings the lists with 0, 1, 2, or 3 `1`s to the front. Pyth's sort is stable, so this ends up with the valid punch sequences at the front.
I'm unsatisfied with the `^U2 12` part of the program to generate the base length-12 lists. If the rest of the program could handle strings, `^`T12` would save a character. If anyone can figure out how to save a character, I'd love to hear about it.
]