Appending a period to a heading conditionally based on headings's content's last character

Why does emph have no effect if before selector.or (likewise smallcaps for H1), but boldface and size change are effective?

As I already said in How to control whitespace before and after run-in headings - #7 by Andrew, constructive show rules must be defined after destructive show rules. Otherwise, you destroy one show rule with another. More specifically, if you don’t just lay out it, then it’s a destructive show rule.

Presumably solution is string conversion with t4t, which is beyond this newcomer’s skill.

How is using a package is beyond newcomer’s skill? If you read the documentation, it should be easy. The t4t package also has docs. Though it’s not perfect, at least right now.

#import "@preview/t4t:0.4.3": get

#let body-size = 11pt
#set par(justify: true, first-line-indent: 1em, spacing: 0.6em)
#set heading(numbering: "1.1.")

#show selector.or(..(2, 3).map(n => heading.where(level: n))): it => {
  counter(heading).display()
  sym.space
  it.body
  if get.text(it).last() not in ".?!:" [.]
}

#show heading.where(level: 1): set align(center)
#show heading.where(level: 1): set text(size: 16pt, weight: "bold")
#show heading.where(level: 1): smallcaps
#show heading.where(level: 2): set text(size: body-size)
#show heading.where(level: 3): set text(size: body-size, weight: "regular")
#show heading.where(level: 3): emph

= Introduction
#lorem(10)

== Motivation?
Looks good with question mark? yes.

=== Background, etc.
Looks good with period.

=== Hello!
Looks good with exclamation mark!

== Failures
=== Colon: bad
Colon is bad.

=== $x=y$ math
Math is mad.

=== Em dash---bad?
Dash --- bad, as RU speakers say (indeed, kinda).

=== En dash is --
En dash is bad.

=== What #emph[else] is bad?
It appears that all that is not strictly plain text without markup significance is bad.

Also, don’t use run-in show rule for the 1st level heading if it’s not supposed to be run-in.