core

Constants and the angle kernel

Purpose

Everything in HORCOM computes in radians, and every angular comparison in the program runs through a small set of helper functions that Robert defined once, in his startup procedure funkt, and used tens of thousands of times. This module is that kernel. It is deliberately the first thing ported, because the aspect logic, the houses and the ephemeris interpolation all depend on its exact branch behaviour.

Origin map

originalported asfile
po, pu, up, puu, pup, pv2, kk (in funkt)kHalfPi, kDegToRad, kRadToDeg, kArcsecToRad, kDegPerCenturyToRad, kTwoPi, kEpssrc/horcom/core/constants.hpp
DEFFN ng(x)norm_degsrc/horcom/core/angle.hpp
DEFFN nb(x)norm_rad
DEFFN nh(x)norm_hours
DEFFN wz(x)deg_in_sign
FUNCTION atn(z, fii)atn(z, n)src/horcom/core/angle.hpp
vergl1, vergl1r, vergl2, vergl2rsame namessrc/horcom/core/angle.hpp

The normalisers

The original definitions read

DEFFN ng(x) = x - 360 * INT(x / 360)
DEFFN nb(x) = x - pv2 * INT(x / pv2)
DEFFN nh(x) = x - 24 * INT(x / 24)
DEFFN wz(x) = x - 30 * FIX(x / 30)

Three of them use INT, the floor. Their results always land in [0, period), a negative angle wraps to the top of the range. The fourth, the degree within a zodiac sign, uses FIX, the truncation, and therefore keeps the sign of its argument. That difference is intentional and the port preserves it through std::floor versus std::trunc.

The quadrant arctangent

GFA BASIC has no atan2, so Robert built his own. The calling pattern throughout the program is fii = ATN(z / n) followed by @atn(z, fii), which adds π when the raw result is negative and adds π again when the numerator is negative. For nonzero arguments the result equals atan2 normalised into [0, 2π), and the tests verify that equivalence over a grid.

Edge worth knowing. For z exactly zero with negative n the original returns 0 where atan2 would return π. The listing never lets that case arise, it adds kk to the denominator in the few places it could. The port keeps the original behaviour and the same guard placement instead of hiding a general guard inside the function.

The comparison reconcilers

Aspect windows live on a circle. When a window straddles the 0 / 2π seam, a plain numeric comparison would tear it apart. Robert solved this with four tiny procedures that push one operand up by a full turn under precise conditions, for example

PROCEDURE vergl2(VAR w1,w2,w3)
  IF w1 > w3 + PI && w3 < w2
    w3 = w3 + pv2
  ENDIF
  IF w1 > w2 + PI && w2 > 0 && w1 < pv2
    w2 = w2 + pv2
  ENDIF
RETURN

These four are transcribed condition for condition. They look asymmetric because they are, each variant serves one specific call site family in the aspect scanner, and the port does not try to unify them.

Verification

tests/test_angle.cpp. Wrap values on both sides of every period, the sign behaviour of deg_in_sign, recovery of full angles from sine and cosine pairs, equivalence of atn with normalised atan2 away from the zero edge, and a seam crossing aspect window that only survives because of vergl2.