I understand now. The fact was that the null partitionParam was always more or less synchronized with inferSP, often in a hacky way.
One of the things I tried to do was to keep the logic for the "user-provided partitioning parameter" a little further separated from the planning and caching. The planner just needs to know whether to infer partitioning or to force an SP or MP plan. It is only as we back out of the planning that we start to ask "If we requested inference for this query, what was inferred?" which may resolve to "MP" OR to a partitioning parameter index or -- if the plan doesn't have parameters and wasn't parameterized -- to a partitioning constant value. It is only when we proceed to dispatch the query that we ask -- do we have an inferred partitioning parameter?
If so, what is its value for this invocation (parameterized out of the query, or -- now -- possibly passed in by the user). @AdHocSPForTest is now handled as more of a special case. It forces an SP plan and uses the tester-provided constant to dispatch the query with no need for a hacked parameter index and less potential for confusing it with an actual query parameter, which it really isn't. There may still be some cleanup to do there -- there is still a parameter index being passed into AdHocPlannerWork which at the moment is looking redundant to me.