(Σ*)² ⟲ Σ* (Round-Trip a String Pair) We're back! And this time with an objective scoring criteria!

This is a challenge I thought of recently which I think is not hard but allows for a wide range of possible solutions. Seems like a perfect fit for the creativity of PPCG users :)
Your Task
Define two functions combine and separate with respective types String × String → String and String → String × String such that separate ∘ combine = id.
More precisely, define two functions (or methods/procedures/etc), which I will refer to as combine and separate, and which exhibit the following characteristics:

When invoked with a single string, separate produces two strings as results. (You may use any reasonable string type. Most languages don't canonically allow producing multiple values, so feel free to to do something equivalent, such as returning a 2-tuple or modifying two global variables.)

When invoked with two strings, combine produces a single string as result. (You may accept input to separate in the same form that combine produces, or any other reasonable form.)

For any two strings a and b, separate(combine(a, b)) must result in two strings which are (structurally) equivalent to a and b.

The above bullets are the only restrictions on combine and separate. So, for instance, combine ∘ separate need not be id, and separate can even be a partial function, if you like.

The functions need not actually be named combine and separate


Intent
My goal with this challenge is to inspire creative and interesting solutions. As such, in order to avoid priming/suggesting your thinking about how to solve this problem, I will not be providing test cases, and I strongly suggest that you do not look at the existing answers until you submit your own.
Winning Criteria
This challenge is code-golf; the answer with the lowest score wins. Score is defined as number of bytes, except that it's -75% if combine ∘ separate = id as well (i.e., if combine is bijective and separate = combine⁻¹).
For brownie points, also try making your solution, in principle,

work on any alphabet rather than char, possibly including alphabets of size- 0, 1, or 2
generalize nicely to \$n\$-tuples or lists


Have fun!
Jelly, 2 bytes
combine
Ṿ

Try it online!
separate
V

Try it online!
A boring, but short, solution. Uses Jelly's repr and eval atoms.
Ṿ is the right-inverse of V, meaning that ṾV (the Jelly way of composing 2 functions) returns the argument unchanged. Therefore, if passed [A, B], ṾV will return [A, B]
The first program takes input as a pair [A, B] and unevals it. The second takes a string as input and evals it as a Jelly program

Just a quick precursor on strings and Jelly. Jelly does not like strings. At all. Internally, strings are just lists of characters to Jelly, and when outputting, Jelly will do all it can to &quot;mash&quot; everything together so that it doesn't look like it should. An example that you'd expect to output ['a', 'b', 'c', 2], or even ['abc', 2]. As such, the Footers of the two programs above contain code to print the strings as-is, as lists of characters. Unfortunately, this can make them difficult to read.
But how do the programs work? Well, most of the work is done by the combine function. The separate function is simply &quot;eval the argument as Jelly code&quot;.
Ṿ
Ṿ is a weird atom, as Jelly atoms go. It's entire purpose is to act as the inverse to V (as a side note, if an character has an &quot;underdot&quot; below it in Jelly, it's likely to be the &quot;inverse&quot; to the atom without that underdot. For example, O/Ọ (ord/chr) or Y/Ỵ (join/split at newlines)) and the function it uses in the interpreter, jelly_uneval, is only used by one other atom, Ṙ, which just prints the same value as Ṿ.
However, Ṿ does a pretty good job at yielding some Jelly code that, when evaluated, yields it's argument. I won't break down into the exact details, but it's able to handle when string delimiters such as “ are contained in the input, so is a pretty solid &quot;uneval&quot; atom, as these things go.